diff --git a/.gitmodules b/.gitmodules index ddc976053..3083181c9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "tests/codegen/wasi-clocks"] path = tests/codegen/wasi-clocks url = https://github.com/WebAssembly/wasi-clocks +[submodule "crates/cpp/tests/wasm-micro-runtime"] + path = crates/cpp/tests/wasm-micro-runtime + url = https://github.com/bytecodealliance/wasm-micro-runtime.git diff --git a/Cargo.lock b/Cargo.lock index 206eeb494..b4da7d04a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -819,7 +819,7 @@ name = "test-helpers" version = "0.0.0" dependencies = [ "codegen-macro", - "wasm-encoder 0.234.0", + "wasm-encoder 0.235.0", "wit-bindgen-core", "wit-component", "wit-parser", @@ -1002,9 +1002,8 @@ checksum = "ddbd7f2a9e3635abe5d4df93b12cadc8d6818079785ee4fab3719ae3c85a064e" [[package]] name = "wasm-compose" -version = "0.234.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7017601e9dbda42ddb74566c8e9691e900254f0b9cf08339a9893831abc4b303" +version = "0.235.0" +source = "git+https://github.com/cpetig/wasm-tools?branch=symmetric#cab9bcb81e7fc2cf96b31e972557ca1a691872f2" dependencies = [ "anyhow", "heck 0.4.1", @@ -1016,8 +1015,8 @@ dependencies = [ "serde_derive", "serde_yaml", "smallvec", - "wasm-encoder 0.234.0", - "wasmparser 0.234.0", + "wasm-encoder 0.235.0", + "wasmparser 0.235.0", "wat", ] @@ -1032,12 +1031,11 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.234.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "170a0157eef517a179f2d20ed7c68df9c3f7f6c1c047782d488bf5a464174684" +version = "0.235.0" +source = "git+https://github.com/cpetig/wasm-tools?branch=symmetric#cab9bcb81e7fc2cf96b31e972557ca1a691872f2" dependencies = [ "leb128fmt", - "wasmparser 0.234.0", + "wasmparser 0.235.0", ] [[package]] @@ -1058,14 +1056,13 @@ dependencies = [ [[package]] name = "wasm-metadata" -version = "0.234.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a42fe3f5cbfb56fc65311ef827930d06189160038e81db62188f66b4bf468e3a" +version = "0.235.0" +source = "git+https://github.com/cpetig/wasm-tools?branch=symmetric#cab9bcb81e7fc2cf96b31e972557ca1a691872f2" dependencies = [ "anyhow", "indexmap", - "wasm-encoder 0.234.0", - "wasmparser 0.234.0", + "wasm-encoder 0.235.0", + "wasmparser 0.235.0", ] [[package]] @@ -1081,9 +1078,8 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.234.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be22e5a8f600afce671dd53c8d2dd26b4b7aa810fd18ae27dfc49737f3e02fc5" +version = "0.235.0" +source = "git+https://github.com/cpetig/wasm-tools?branch=symmetric#cab9bcb81e7fc2cf96b31e972557ca1a691872f2" dependencies = [ "bitflags", "hashbrown", @@ -1094,22 +1090,20 @@ dependencies = [ [[package]] name = "wast" -version = "234.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5fc6bea84cc3007ad3e68c6223f52095e6093eec4d7ebdff355f2c952fd9007" +version = "235.0.0" +source = "git+https://github.com/cpetig/wasm-tools?branch=symmetric#cab9bcb81e7fc2cf96b31e972557ca1a691872f2" dependencies = [ "bumpalo", "leb128fmt", "memchr", "unicode-width 0.2.0", - "wasm-encoder 0.234.0", + "wasm-encoder 0.235.0", ] [[package]] name = "wat" -version = "1.234.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a4807d67cf6885965d2f118744fd0ad20ffb9082c875de532af37b499e65aa6" +version = "1.235.0" +source = "git+https://github.com/cpetig/wasm-tools?branch=symmetric#cab9bcb81e7fc2cf96b31e972557ca1a691872f2" dependencies = [ "wast", ] @@ -1204,6 +1198,17 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen-bridge" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "heck 0.5.0", + "wit-bindgen-core", + "wit-component", +] + [[package]] name = "wit-bindgen-c" version = "0.42.1" @@ -1212,8 +1217,8 @@ dependencies = [ "clap", "heck 0.5.0", "indexmap", - "wasm-encoder 0.234.0", - "wasm-metadata 0.234.0", + "wasm-encoder 0.235.0", + "wasm-metadata 0.235.0", "wit-bindgen-core", "wit-component", ] @@ -1225,7 +1230,8 @@ dependencies = [ "anyhow", "clap", "env_logger", - "wasm-encoder 0.234.0", + "wasm-encoder 0.235.0", + "wit-bindgen-bridge", "wit-bindgen-c", "wit-bindgen-core", "wit-bindgen-cpp", @@ -1256,8 +1262,8 @@ dependencies = [ "clap", "heck 0.5.0", "test-helpers", - "wasm-encoder 0.234.0", - "wasm-metadata 0.234.0", + "wasm-encoder 0.235.0", + "wasm-metadata 0.235.0", "wit-bindgen-c", "wit-bindgen-core", "wit-component", @@ -1271,7 +1277,7 @@ dependencies = [ "clap", "heck 0.5.0", "indexmap", - "wasm-metadata 0.234.0", + "wasm-metadata 0.235.0", "wit-bindgen-core", "wit-component", "wit-parser", @@ -1321,7 +1327,7 @@ dependencies = [ "serde_json", "syn", "test-helpers", - "wasm-metadata 0.234.0", + "wasm-metadata 0.235.0", "wit-bindgen", "wit-bindgen-core", "wit-bindgen-rt", @@ -1359,8 +1365,8 @@ dependencies = [ "wac-types", "wasi-preview1-component-adapter-provider", "wasm-compose", - "wasm-encoder 0.234.0", - "wasmparser 0.234.0", + "wasm-encoder 0.235.0", + "wasmparser 0.235.0", "wat", "wit-bindgen-csharp", "wit-component", @@ -1369,9 +1375,8 @@ dependencies = [ [[package]] name = "wit-component" -version = "0.234.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a8888169acf4c6c4db535beb405b570eedac13215d6821ca9bd03190f7f8b8c" +version = "0.235.0" +source = "git+https://github.com/cpetig/wasm-tools?branch=symmetric#cab9bcb81e7fc2cf96b31e972557ca1a691872f2" dependencies = [ "anyhow", "bitflags", @@ -1380,18 +1385,17 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder 0.234.0", - "wasm-metadata 0.234.0", - "wasmparser 0.234.0", + "wasm-encoder 0.235.0", + "wasm-metadata 0.235.0", + "wasmparser 0.235.0", "wat", "wit-parser", ] [[package]] name = "wit-parser" -version = "0.234.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465492df47d8dcc015a3b7f241aed8ea03688fee7c5e04162285c5b1a3539c8b" +version = "0.235.0" +source = "git+https://github.com/cpetig/wasm-tools?branch=symmetric#cab9bcb81e7fc2cf96b31e972557ca1a691872f2" dependencies = [ "anyhow", "id-arena", @@ -1402,5 +1406,5 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.234.0", + "wasmparser 0.235.0", ] diff --git a/Cargo.toml b/Cargo.toml index a3d2a185a..7ed1a8b55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ repository = "https://github.com/bytecodealliance/wit-bindgen" [workspace.dependencies] anyhow = "1.0.72" bitflags = "2.3.3" -heck = { version = "0.5" } +heck = { version = "0.5" } pulldown-cmark = { version = "0.9", default-features = false } serde = { version = "1.0.218", features = ["derive"] } clap = { version = "4.3.19", features = ["derive"] } @@ -31,13 +31,13 @@ prettyplease = "0.2.20" syn = { version = "2.0.89", features = ["printing"] } futures = "0.3.31" -wat = "1.234.0" -wasmparser = "0.234.0" -wasm-encoder = "0.234.0" -wasm-metadata = { version = "0.234.0", default-features = false } -wit-parser = "0.234.0" -wit-component = "0.234.0" -wasm-compose = "0.234.0" +wat = { git = "https://github.com/cpetig/wasm-tools", branch = "symmetric" } +wasmparser = { git = "https://github.com/cpetig/wasm-tools", branch = "symmetric" } +wasm-encoder = { git = "https://github.com/cpetig/wasm-tools", branch = "symmetric" } +wasm-metadata = { git = "https://github.com/cpetig/wasm-tools", branch = "symmetric", default-features = false } +wit-parser = { git = "https://github.com/cpetig/wasm-tools", branch = "symmetric" } +wit-component = { git = "https://github.com/cpetig/wasm-tools", branch = "symmetric" } +wasm-compose = { git = "https://github.com/cpetig/wasm-tools", branch = "symmetric" } wit-bindgen-core = { path = 'crates/core', version = '0.42.1' } wit-bindgen-c = { path = 'crates/c', version = '0.42.1' } @@ -49,6 +49,8 @@ wit-bindgen-moonbit = { path = 'crates/moonbit', version = '0.42.1' } wit-bindgen = { path = 'crates/guest-rust', version = '0.42.1', default-features = false } wit-bindgen-test = { path = 'crates/test', version = '0.42.1' } +wit-bindgen-bridge = { path = 'crates/bridge', version = '0.1.0' } + [[bin]] name = "wit-bindgen" @@ -65,6 +67,7 @@ wit-bindgen-csharp = { workspace = true, features = ['clap'], optional = true } wit-bindgen-test = { workspace = true } wit-component = { workspace = true } wasm-encoder = { workspace = true } +wit-bindgen-bridge = { workspace = true, features = ['clap'], optional = true } env_logger = "0.11.7" [features] @@ -75,9 +78,11 @@ default = [ 'go', 'csharp', 'cpp', + 'bridge', 'moonbit', 'async', ] +bridge = ['dep:wit-bindgen-bridge'] c = ['dep:wit-bindgen-c'] cpp = ['dep:wit-bindgen-cpp'] rust = ['dep:wit-bindgen-rust'] diff --git a/README.md b/README.md index 904172cf9..ef404715c 100644 --- a/README.md +++ b/README.md @@ -393,6 +393,9 @@ WIT types. This relies on wasi-SDK for guest compilation. +A separate subcommand (cpp-host) will generate C++ host code for +WebAssembly micro runtime. + ### Guest: MoonBit MoonBit can be compiled to WebAssembly using [its toolchain](https://moonbitlang.com/download): @@ -485,6 +488,8 @@ components: generate Python source code to interact with the component using an embedding of Wasmtime for its core WebAssembly support. +- C++-17+: see above chapter for WAMR host code generation. + - Ruby: the [`wasmtime-rb`](https://github.com/bytecodealliance/wasmtime-rb) project has initial support for components since [v27](https://github.com/bytecodealliance/wasmtime-rb/releases/tag/v27.0.0). diff --git a/TODO.md b/TODO.md index 5d08da9df..56eb26d56 100644 --- a/TODO.md +++ b/TODO.md @@ -58,3 +58,10 @@ * Imported handle types show up as `any` in TS, unsure how to plumb through actual types to get that actually typed. + +# Cpp + +* Nested lists +* Host: Strings inside records +* Strings test: return-unicode should get out parameter + diff --git a/crates/bridge/Cargo.toml b/crates/bridge/Cargo.toml new file mode 100644 index 000000000..b41890a92 --- /dev/null +++ b/crates/bridge/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "wit-bindgen-bridge" +authors = ["Christof Petig "] +version = "0.1.0" +edition.workspace = true +repository = 'https://github.com/cpetig/wit-bindgen' +license = "Apache-2.0 WITH LLVM-exception" +description = """ +Bridge binding (forwarding) generator for WIT and the component model, targeting w2c2 and wamr. +""" + +[lib] +doctest = false +test = false + +[dependencies] +wit-bindgen-core = { workspace = true } +wit-component = { workspace = true } +anyhow = { workspace = true } +heck = { workspace = true } +clap = { workspace = true, optional = true } + +#[dev-dependencies] +#test-helpers = { path = '../test-helpers' } diff --git a/crates/bridge/src/lib.rs b/crates/bridge/src/lib.rs new file mode 100644 index 000000000..dd39c83db --- /dev/null +++ b/crates/bridge/src/lib.rs @@ -0,0 +1,240 @@ +use std::fmt::Write; +use wit_bindgen_core::{ + abi::{AbiVariant, WasmType}, + make_external_symbol, uwriteln, + wit_parser::{self, Function, Resolve, TypeOwner, WorldId, WorldKey}, + Source, WorldGenerator, +}; + +#[derive(Default)] +struct Bridge { + src: Source, + opts: Opts, +} + +#[derive(Default, Debug, Clone)] +#[cfg_attr(feature = "clap", derive(clap::Args))] +pub struct Opts { + /// Output bridge code for webassembly micro runtime + #[cfg_attr(feature = "clap", arg(long))] + wamr: bool, + /// w2c2 Instance name (derived from wasm file) + #[cfg_attr(feature = "clap", arg(long, default_value_t = String::default()))] + instance: String, + /// w2c2 Include name + #[cfg_attr(feature = "clap", arg(long, default_value_t = String::default()))] + include: String, +} + +impl Opts { + pub fn build(&self) -> Box { + let mut r = Bridge::default(); + r.opts = self.clone(); + Box::new(r) + } +} + +impl WorldGenerator for Bridge { + fn preprocess(&mut self, resolve: &Resolve, world: WorldId) { + let world = &resolve.worlds[world]; + let name = if self.opts.instance.is_empty() { + world.name.clone() + } else { + self.opts.instance.clone() + }; + let include = if self.opts.include.is_empty() { + name.clone() + ".h" + } else { + self.opts.include.clone() + }; + uwriteln!( + self.src, + r#" + #include + #include + #include "{include}" + + static {name}Instance* instance; + static {name}Instance app_instance; + + void trap(Trap trap) {{ + abort(); + }} + + {name}Instance* get_app() {{ + if (!instance) {{ + {name}Instantiate(&app_instance, NULL); + instance = &app_instance; + }} + return instance; + }} + "# + ); + } + + fn import_interface( + &mut self, + resolve: &wit_parser::Resolve, + name: &WorldKey, + iface: wit_parser::InterfaceId, + _files: &mut wit_bindgen_core::Files, + ) -> anyhow::Result<()> { + let world = match name { + WorldKey::Name(n) => n.clone(), + WorldKey::Interface(i) => resolve.interfaces[*i].name.clone().unwrap_or_default(), + }; + uwriteln!(self.src, "// Import IF {world}"); + + let mut gen = self.interface(resolve); + for (_name, func) in resolve.interfaces[iface].functions.iter() { + gen.generate_function(func, &TypeOwner::Interface(iface), AbiVariant::GuestImport); + } + Ok(()) + } + + fn export_interface( + &mut self, + resolve: &wit_parser::Resolve, + name: &WorldKey, + iface: wit_parser::InterfaceId, + _files: &mut wit_bindgen_core::Files, + ) -> anyhow::Result<()> { + let world = match name { + WorldKey::Name(n) => n.clone(), + WorldKey::Interface(i) => resolve.interfaces[*i].name.clone().unwrap_or_default(), + }; + uwriteln!(self.src, "// Export IF {world}"); + + let mut gen = self.interface(resolve); + for (_name, func) in resolve.interfaces[iface].functions.iter() { + gen.generate_function(func, &TypeOwner::Interface(iface), AbiVariant::GuestExport); + } + Ok(()) + } + + fn import_funcs( + &mut self, + resolve: &wit_parser::Resolve, + worldid: wit_parser::WorldId, + funcs: &[(&str, &wit_parser::Function)], + _files: &mut wit_bindgen_core::Files, + ) { + let world = &resolve.worlds[worldid]; + uwriteln!(self.src, "// Import Funcs {}", world.name); + let mut gen = self.interface(resolve); + for (_name, func) in funcs.iter() { + gen.generate_function(func, &TypeOwner::World(worldid), AbiVariant::GuestImport); + } + } + + fn export_funcs( + &mut self, + resolve: &wit_parser::Resolve, + worldid: wit_parser::WorldId, + funcs: &[(&str, &wit_parser::Function)], + _files: &mut wit_bindgen_core::Files, + ) -> anyhow::Result<()> { + let world = &resolve.worlds[worldid]; + uwriteln!(self.src, "// Export Funcs {}", world.name); + let mut gen = self.interface(resolve); + for (_name, func) in funcs.iter() { + gen.generate_function(func, &TypeOwner::World(worldid), AbiVariant::GuestExport); + } + Ok(()) + } + + fn import_types( + &mut self, + resolve: &wit_parser::Resolve, + world: wit_parser::WorldId, + _types: &[(&str, wit_parser::TypeId)], + _files: &mut wit_bindgen_core::Files, + ) { + let world = &resolve.worlds[world]; + uwriteln!(self.src, "// Import Types {}", world.name); + } + + fn finish( + &mut self, + resolve: &wit_parser::Resolve, + world: wit_parser::WorldId, + files: &mut wit_bindgen_core::Files, + ) -> anyhow::Result<()> { + let world = &resolve.worlds[world]; + files.push(&format!("{}_bridge.c", world.name), self.src.as_bytes()); + Ok(()) + } +} + +impl Bridge { + fn interface<'a>(&'a mut self, resolve: &'a Resolve) -> BridgeInterfaceGenerator<'a> { + BridgeInterfaceGenerator { gen: self, resolve } + } + + fn wasm_type(&self, ty: WasmType, _var: TypeVariant) -> String { + match ty { + WasmType::I32 => todo!(), + WasmType::I64 => todo!(), + WasmType::F32 => todo!(), + WasmType::F64 => todo!(), + WasmType::Pointer => todo!(), + WasmType::PointerOrI64 => todo!(), + WasmType::Length => todo!(), + } + } + + fn func_name( + &self, + resolve: &Resolve, + func: &Function, + owner: &TypeOwner, + variant: AbiVariant, + ) -> String { + let module_name = match owner { + TypeOwner::World(_) => todo!(), + TypeOwner::Interface(i) => resolve.interfaces[*i].name.clone().unwrap_or_default(), + TypeOwner::None => todo!(), + }; + make_external_symbol(&module_name, &func.name, variant) + } +} + +struct BridgeInterfaceGenerator<'a> { + gen: &'a mut Bridge, + resolve: &'a Resolve, +} + +enum TypeVariant { + W2C2, + Native, +} + +impl<'a> BridgeInterfaceGenerator<'a> { + fn generate_function(&mut self, func: &Function, owner: &TypeOwner, variant: AbiVariant) { + uwriteln!(self.gen.src, "// Func {} {:?}", func.name, variant); + let result_var = match variant { + AbiVariant::GuestImport => TypeVariant::W2C2, + AbiVariant::GuestExport => TypeVariant::Native, + AbiVariant::GuestImportAsync => todo!(), + AbiVariant::GuestExportAsync => todo!(), + AbiVariant::GuestExportAsyncStackful => todo!(), + }; + let signature = self.resolve.wasm_signature(variant, func); + let return_via_pointer = signature.retptr; + let is_export = matches!(variant, AbiVariant::GuestExport); + if is_export { + self.gen + .src + .push_str(r#"__attribute__ ((visibility ("default"))) "#); + } + let res = if signature.results.is_empty() || return_via_pointer { + "void".into() + } else { + self.gen.wasm_type(signature.results[0], result_var) + }; + self.gen.src.push_str(&res); + self.gen.src.push_str(" "); + let fname = self.gen.func_name(self.resolve, func, owner, variant); + self.gen.src.push_str(&fname); + } +} diff --git a/crates/c/src/lib.rs b/crates/c/src/lib.rs index 643ae42bf..769badceb 100644 --- a/crates/c/src/lib.rs +++ b/crates/c/src/lib.rs @@ -4028,4 +4028,4 @@ pub fn to_c_ident(name: &str) -> String { } } -const POINTER_SIZE_EXPRESSION: &str = "sizeof(void*)"; +pub const POINTER_SIZE_EXPRESSION: &str = "sizeof(void*)"; diff --git a/crates/core/src/abi.rs b/crates/core/src/abi.rs index 102458e67..6ce6fdef6 100644 --- a/crates/core/src/abi.rs +++ b/crates/core/src/abi.rs @@ -493,6 +493,7 @@ def_instruction! { CallWasm { name: &'a str, sig: &'a WasmSignature, + module_prefix: &'a str, } : [sig.params.len()] => [sig.results.len()], /// Same as `CallWasm`, except the dual where an interface is being @@ -640,6 +641,8 @@ pub enum LiftLower { /// SourceLanguage --lower-args--> Wasm; call; Wasm --lift-results--> SourceLanguage /// ``` LowerArgsLiftResults, + /// Symmetric calling convention + Symmetric, } /// Trait for language implementors to use to generate glue code between native @@ -739,7 +742,13 @@ pub fn call( bindgen: &mut impl Bindgen, async_: bool, ) { - Generator::new(resolve, bindgen).call(func, variant, lift_lower, async_); + if matches!(lift_lower, LiftLower::Symmetric) { + let sig = wasm_signature_symmetric(resolve, variant, func, true); + Generator::new(resolve, bindgen, true) + .call_with_signature(func, sig, variant, lift_lower, async_); + } else { + Generator::new(resolve, bindgen, false).call(func, variant, lift_lower, async_); + } } pub fn lower_to_memory( @@ -749,7 +758,7 @@ pub fn lower_to_memory( value: B::Operand, ty: &Type, ) { - let mut generator = Generator::new(resolve, bindgen); + let mut generator = Generator::new(resolve, bindgen, false); // TODO: make this configurable? Right now this function is only called for // future/stream callbacks so it's appropriate to skip realloc here as it's // all "lower for wasm import", but this might get reused for something else @@ -765,7 +774,7 @@ pub fn lower_flat( value: B::Operand, ty: &Type, ) -> Vec { - let mut generator = Generator::new(resolve, bindgen); + let mut generator = Generator::new(resolve, bindgen, false); generator.stack.push(value); generator.realloc = Some(Realloc::Export("cabi_realloc")); generator.lower(ty); @@ -778,7 +787,7 @@ pub fn lift_from_memory( address: B::Operand, ty: &Type, ) -> B::Operand { - let mut generator = Generator::new(resolve, bindgen); + let mut generator = Generator::new(resolve, bindgen, false); generator.read_from_memory(ty, address, Default::default()); generator.stack.pop().unwrap() } @@ -790,7 +799,7 @@ pub fn lift_from_memory( /// functions and will primarily generate `GuestDeallocate*` instructions, /// plus others used as input to those instructions. pub fn post_return(resolve: &Resolve, func: &Function, bindgen: &mut impl Bindgen) { - Generator::new(resolve, bindgen).post_return(func); + Generator::new(resolve, bindgen, false).post_return(func); } /// Returns whether the `Function` specified needs a post-return function to @@ -859,7 +868,7 @@ pub fn deallocate_lists_in_types( indirect: bool, bindgen: &mut B, ) { - Generator::new(resolve, bindgen).deallocate_in_types( + Generator::new(resolve, bindgen, false).deallocate_in_types( types, operands, indirect, @@ -876,7 +885,7 @@ pub fn deallocate_lists_and_own_in_types( indirect: bool, bindgen: &mut B, ) { - Generator::new(resolve, bindgen).deallocate_in_types( + Generator::new(resolve, bindgen, false).deallocate_in_types( types, operands, indirect, @@ -917,12 +926,13 @@ struct Generator<'a, B: Bindgen> { stack: Vec, return_pointer: Option, realloc: Option, + symmetric: bool, } const MAX_FLAT_PARAMS: usize = 16; impl<'a, B: Bindgen> Generator<'a, B> { - fn new(resolve: &'a Resolve, bindgen: &'a mut B) -> Generator<'a, B> { + fn new(resolve: &'a Resolve, bindgen: &'a mut B, symmetric: bool) -> Generator<'a, B> { Generator { resolve, bindgen, @@ -931,11 +941,24 @@ impl<'a, B: Bindgen> Generator<'a, B> { stack: Vec::new(), return_pointer: None, realloc: None, + symmetric, } } fn call(&mut self, func: &Function, variant: AbiVariant, lift_lower: LiftLower, async_: bool) { let sig = self.resolve.wasm_signature(variant, func); + self.call_with_signature(func, sig, variant, lift_lower, async_); + } + + fn call_with_signature( + &mut self, + func: &Function, + sig: WasmSignature, + variant: AbiVariant, + lift_lower: LiftLower, + async_: bool, + ) { + // const MAX_FLAT_PARAMS: usize = 16; // Lowering parameters calling a wasm import _or_ returning a result // from an async-lifted wasm export means we don't need to pass @@ -953,8 +976,11 @@ impl<'a, B: Bindgen> Generator<'a, B> { }; assert!(self.realloc.is_none()); - match lift_lower { - LiftLower::LowerArgsLiftResults => { + let language_to_abi = matches!(lift_lower, LiftLower::LowerArgsLiftResults) + || (matches!(lift_lower, LiftLower::Symmetric) + && matches!(variant, AbiVariant::GuestImport)); + match language_to_abi { + true => { assert!(!async_, "generators should not be using this for async"); self.realloc = Some(realloc); @@ -1014,12 +1040,15 @@ impl<'a, B: Bindgen> Generator<'a, B> { } self.realloc = None; + let mut retptr_oprnd = None; + // If necessary we may need to prepare a return pointer for // this ABI. if variant == AbiVariant::GuestImport && sig.retptr { let info = self.bindgen.sizes().params(&func.result); let ptr = self.bindgen.return_pointer(info.size, info.align); self.return_pointer = Some(ptr.clone()); + retptr_oprnd = Some(ptr.clone()); self.stack.push(ptr); } @@ -1027,9 +1056,18 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&Instruction::CallWasm { name: &func.name, sig: &sig, + module_prefix: Default::default(), }); - if !sig.retptr { + if matches!(lift_lower, LiftLower::Symmetric) && sig.retptr { + if let Some(ptr) = retptr_oprnd { + self.read_results_from_memory( + &func.result, + ptr.clone(), + Default::default(), + ); + } + } else if !(sig.retptr || async_) { // With no return pointer in use we can simply lift the // result(s) of the function from the result of the core // wasm function. @@ -1075,7 +1113,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { amt: usize::from(func.result.is_some()), }); } - LiftLower::LiftArgsLowerResults => { + false => { if let (AbiVariant::GuestImport, true) = (variant, async_) { todo!("implement host-side support for async lift/lower"); } @@ -1154,14 +1192,14 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.lower(ty); } } else { - match variant { + match variant == AbiVariant::GuestImport || lift_lower == LiftLower::Symmetric { // When a function is imported to a guest this means // it's a host providing the implementation of the // import. The result is stored in the pointer // specified in the last argument, so we get the // pointer here and then write the return value into // it. - AbiVariant::GuestImport => { + true => { self.emit(&Instruction::GetArg { nth: sig.params.len() - 1, }); @@ -1174,7 +1212,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { // value was stored at. Allocate some space here // (statically) and then write the result into that // memory, returning the pointer at the end. - AbiVariant::GuestExport | AbiVariant::GuestExportAsync => { + false => { let ElementInfo { size, align } = self.bindgen.sizes().params(&func.result); let ptr = self.bindgen.return_pointer(size, align); @@ -1183,12 +1221,21 @@ impl<'a, B: Bindgen> Generator<'a, B> { ptr.clone(), Default::default(), ); - self.stack.push(ptr); - } + if !matches!(lift_lower, LiftLower::Symmetric) { + self.stack.push(ptr); + } + } // AbiVariant::GuestImportAsync + // | AbiVariant::GuestExportAsync + // | AbiVariant::GuestExportAsyncStackful => { + // unreachable!() + // } + } - AbiVariant::GuestImportAsync | AbiVariant::GuestExportAsyncStackful => { - unreachable!() - } + if matches!( + variant, + AbiVariant::GuestImportAsync | AbiVariant::GuestExportAsyncStackful + ) { + unreachable!() } } @@ -1514,6 +1561,8 @@ impl<'a, B: Bindgen> Generator<'a, B> { use Instruction::*; match *ty { + // Builtin types need different flavors of storage instructions + // depending on the size of the value written. Type::Bool => self.emit(&BoolFromI32), Type::S8 => self.emit(&S8FromI32), Type::U8 => self.emit(&U8FromI32), @@ -1697,8 +1746,6 @@ impl<'a, B: Bindgen> Generator<'a, B> { use Instruction::*; match *ty { - // Builtin types need different flavors of storage instructions - // depending on the size of the value written. Type::Bool | Type::U8 | Type::S8 => { self.lower_and_emit(ty, addr, &I32Store8 { offset }) } @@ -1717,7 +1764,11 @@ impl<'a, B: Bindgen> Generator<'a, B> { TypeDefKind::List(_) => self.write_list_to_memory(ty, addr, offset), TypeDefKind::Future(_) | TypeDefKind::Stream(_) | TypeDefKind::Handle(_) => { - self.lower_and_emit(ty, addr, &I32Store { offset }) + if self.symmetric { + self.lower_and_emit(ty, addr, &PointerStore { offset }) + } else { + self.lower_and_emit(ty, addr, &I32Store { offset }) + } } // Decompose the record into its components and then write all @@ -1910,7 +1961,11 @@ impl<'a, B: Bindgen> Generator<'a, B> { TypeDefKind::List(_) => self.read_list_from_memory(ty, addr, offset), TypeDefKind::Future(_) | TypeDefKind::Stream(_) | TypeDefKind::Handle(_) => { - self.emit_and_lift(ty, addr, &I32Load { offset }) + if self.symmetric { + self.emit_and_lift(ty, addr, &PointerLoad { offset }) + } else { + self.emit_and_lift(ty, addr, &I32Load { offset }) + } } TypeDefKind::Resource => { @@ -2214,7 +2269,6 @@ impl<'a, B: Bindgen> Generator<'a, B> { }); self.deallocate(ty, what); } - Type::Bool | Type::U8 | Type::S8 @@ -2228,7 +2282,6 @@ impl<'a, B: Bindgen> Generator<'a, B> { | Type::F32 | Type::F64 | Type::ErrorContext => {} - Type::Id(id) => match &self.resolve.types[id].kind { TypeDefKind::Type(t) => self.deallocate_indirect(t, addr, offset, what), @@ -2399,6 +2452,15 @@ fn cast(from: WasmType, to: WasmType) -> Bitcast { } } +pub fn wasm_signature_symmetric( + resolve: &Resolve, + variant: AbiVariant, + func: &Function, + symmetric: bool, +) -> WasmSignature { + resolve.wasm_signature_symmetric(variant, func, symmetric) +} + fn flat_types(resolve: &Resolve, ty: &Type) -> Option> { let mut storage = [WasmType::I32; MAX_FLAT_PARAMS]; let mut flat = FlatTypes::new(&mut storage); diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 2932b7b4b..dc2fde01b 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -13,6 +13,7 @@ pub use types::{TypeInfo, Types}; mod path; pub use path::name_package_module; mod async_; +pub mod symmetric; pub use async_::AsyncFilterSet; #[derive(Default, Copy, Clone, PartialEq, Eq, Debug)] @@ -132,6 +133,8 @@ pub trait WorldGenerator { files: &mut Files, ); fn finish(&mut self, resolve: &Resolve, world: WorldId, files: &mut Files) -> Result<()>; + // modify resolve by command line options + fn apply_resolve_options(&mut self, _resolve: &mut Resolve, _world: &mut WorldId) {} } /// This is a possible replacement for the `Generator` trait above, currently @@ -235,3 +238,52 @@ pub fn dealias(resolve: &Resolve, mut id: TypeId) -> TypeId { } } } + +fn hexdigit(v: u32) -> char { + if v < 10 { + char::from_u32(('0' as u32) + v).unwrap() + } else { + char::from_u32(('A' as u32) - 10 + v).unwrap() + } +} + +/// encode symbol as alphanumeric by hex-encoding special characters +pub fn make_external_component(input: &str) -> String { + input + .chars() + .map(|c| match c { + 'A'..='Z' | 'a'..='z' | '0'..='9' | '_' => { + let mut s = String::new(); + s.push(c); + s + } + '-' => { + let mut s = String::new(); + s.push('_'); + s + } + _ => { + let mut s = String::from("X"); + s.push(hexdigit((c as u32 & 0xf0) >> 4)); + s.push(hexdigit(c as u32 & 0xf)); + s + } + }) + .collect() +} + +/// encode symbol as alphanumeric by hex-encoding special characters +pub fn make_external_symbol(module_name: &str, name: &str, variant: abi::AbiVariant) -> String { + if module_name.is_empty() || module_name == "$root" { + make_external_component(name) + } else { + let mut res = make_external_component(module_name); + res.push_str(if matches!(variant, abi::AbiVariant::GuestExport) { + "X23" // Hash character + } else { + "X00" // NUL character (some tools use '.' for display) + }); + res.push_str(&make_external_component(name)); + res + } +} diff --git a/crates/core/src/symmetric.rs b/crates/core/src/symmetric.rs new file mode 100644 index 000000000..1a3f4a019 --- /dev/null +++ b/crates/core/src/symmetric.rs @@ -0,0 +1,185 @@ +// helper functions for symmetric ABI + +use wit_parser::{Resolve, Type, TypeDefKind}; + +// figure out whether deallocation is needed in the caller +fn needs_dealloc2(resolve: &Resolve, tp: &Type) -> bool { + match tp { + Type::Bool + | Type::U8 + | Type::U16 + | Type::U32 + | Type::U64 + | Type::S8 + | Type::S16 + | Type::S32 + | Type::S64 + | Type::F32 + | Type::F64 + | Type::Char => false, + Type::String => true, + Type::Id(id) => match &resolve.types[*id].kind { + TypeDefKind::Enum(_) => false, + TypeDefKind::Record(r) => r.fields.iter().any(|f| needs_dealloc2(resolve, &f.ty)), + TypeDefKind::Resource => false, + TypeDefKind::Handle(_) => false, + TypeDefKind::Flags(_) => false, + TypeDefKind::Tuple(t) => t.types.iter().any(|f| needs_dealloc2(resolve, f)), + TypeDefKind::Variant(v) => v + .cases + .iter() + .any(|c| c.ty.map_or(false, |t| needs_dealloc2(resolve, &t))), + TypeDefKind::Option(tp) => needs_dealloc2(resolve, tp), + TypeDefKind::Result(r) => { + r.ok.as_ref() + .map_or(false, |tp| needs_dealloc2(resolve, tp)) + || r.err + .as_ref() + .map_or(false, |tp| needs_dealloc2(resolve, tp)) + } + TypeDefKind::List(_l) => true, + TypeDefKind::Future(_) => todo!(), + TypeDefKind::Stream(_) => todo!(), + TypeDefKind::Type(tp) => needs_dealloc2(resolve, tp), + TypeDefKind::Unknown => false, + TypeDefKind::FixedSizeList(_, _) => todo!(), + }, + Type::ErrorContext => todo!(), + } +} + +pub fn needs_dealloc(resolve: &Resolve, args: &[(String, Type)]) -> bool { + for (_n, t) in args { + if needs_dealloc2(resolve, t) { + return true; + } + } + return false; +} + +fn has_non_canonical_list2(resolve: &Resolve, ty: &Type, maybe: bool) -> bool { + match ty { + Type::Bool + | Type::U8 + | Type::U16 + | Type::U32 + | Type::U64 + | Type::S8 + | Type::S16 + | Type::S32 + | Type::S64 + | Type::F32 + | Type::F64 + | Type::Char + | Type::String => false, + Type::Id(id) => match &resolve.types[*id].kind { + TypeDefKind::Record(r) => r + .fields + .iter() + .any(|field| has_non_canonical_list2(resolve, &field.ty, maybe)), + TypeDefKind::Resource | TypeDefKind::Handle(_) | TypeDefKind::Flags(_) => false, + TypeDefKind::Tuple(t) => t + .types + .iter() + .any(|ty| has_non_canonical_list2(resolve, ty, maybe)), + TypeDefKind::Variant(var) => var.cases.iter().any(|case| { + case.ty + .as_ref() + .map_or(false, |ty| has_non_canonical_list2(resolve, ty, maybe)) + }), + TypeDefKind::Enum(_) => false, + TypeDefKind::Option(ty) => has_non_canonical_list2(resolve, ty, maybe), + TypeDefKind::Result(res) => { + res.ok + .as_ref() + .map_or(false, |ty| has_non_canonical_list2(resolve, ty, maybe)) + || res + .err + .as_ref() + .map_or(false, |ty| has_non_canonical_list2(resolve, ty, maybe)) + } + TypeDefKind::List(ty) => { + if maybe { + true + } else { + has_non_canonical_list2(resolve, ty, true) + } + } + TypeDefKind::Future(_) | TypeDefKind::Stream(_) => false, + TypeDefKind::Type(ty) => has_non_canonical_list2(resolve, ty, maybe), + TypeDefKind::Unknown => false, + TypeDefKind::FixedSizeList(_, _) => todo!(), + }, + Type::ErrorContext => todo!(), + } +} + +// fn has_non_canonical_list(resolve: &Resolve, results: &Results) -> bool { +// match results { +// Results::Named(vec) => vec +// .iter() +// .any(|(_, ty)| has_non_canonical_list2(resolve, ty, false)), +// Results::Anon(one) => has_non_canonical_list2(resolve, &one, false), +// } +// } + +pub fn has_non_canonical_list(resolve: &Resolve, args: &[(String, Type)]) -> bool { + args.iter() + .any(|(_, ty)| has_non_canonical_list2(resolve, ty, false)) +} + +fn has_non_canonical_list_rust2(resolve: &Resolve, ty: &Type) -> bool { + match ty { + Type::Bool + | Type::U8 + | Type::U16 + | Type::U32 + | Type::U64 + | Type::S8 + | Type::S16 + | Type::S32 + | Type::S64 + | Type::F32 + | Type::F64 + | Type::Char + | Type::String => false, + Type::Id(id) => match &resolve.types[*id].kind { + TypeDefKind::Record(r) => r + .fields + .iter() + .any(|field| has_non_canonical_list_rust2(resolve, &field.ty)), + TypeDefKind::Resource | TypeDefKind::Handle(_) | TypeDefKind::Flags(_) => false, + TypeDefKind::Tuple(t) => t + .types + .iter() + .any(|ty| has_non_canonical_list_rust2(resolve, ty)), + TypeDefKind::Variant(var) => var.cases.iter().any(|case| { + case.ty + .as_ref() + .map_or(false, |ty| has_non_canonical_list_rust2(resolve, ty)) + }), + TypeDefKind::Enum(_) => false, + TypeDefKind::Option(ty) => has_non_canonical_list_rust2(resolve, ty), + TypeDefKind::Result(res) => { + res.ok + .as_ref() + .map_or(false, |ty| has_non_canonical_list_rust2(resolve, ty)) + || res + .err + .as_ref() + .map_or(false, |ty| has_non_canonical_list_rust2(resolve, ty)) + } + TypeDefKind::List(_ty) => true, + TypeDefKind::Future(_) | TypeDefKind::Stream(_) => false, + TypeDefKind::Type(ty) => has_non_canonical_list_rust2(resolve, ty), + TypeDefKind::Unknown => false, + TypeDefKind::FixedSizeList(_, _) => todo!(), + }, + Type::ErrorContext => todo!(), + } +} + +pub fn has_non_canonical_list_rust(resolve: &Resolve, args: &[(String, Type)]) -> bool { + args.iter() + .any(|(_, ty)| has_non_canonical_list_rust2(resolve, ty)) +} diff --git a/crates/cpp/DESIGN.md b/crates/cpp/DESIGN.md index c750a906c..bb6610c94 100644 --- a/crates/cpp/DESIGN.md +++ b/crates/cpp/DESIGN.md @@ -96,3 +96,32 @@ For now for functions the guest import convention is used in both directions: with one modification: Resource IDs become usize, so you can optimize the resource table away. + +## Structs proposal + +See also the explanation of ownership at https://docs.rs/wit-bindgen/0.42.1/wit_bindgen/macro.generate.html + +``` +resource r; +record d { s: string, l: list } +arg: func(d: d); +result: func() -> d; +``` + +``` +struct DResult { + wit::string s; + wit::list l; +} +struct DParam { + std::string_view s; + std::span l; +} +``` + +|direction|style| +|---|---| +|GIA|void arg(DParam d);| +|GIR|DResult result();| +|GEA|void arg(DResult d);| +|GER|DResult result();| diff --git a/crates/cpp/helper-types/wit-host.h b/crates/cpp/helper-types/wit-host.h new file mode 100644 index 000000000..bd9ae704f --- /dev/null +++ b/crates/cpp/helper-types/wit-host.h @@ -0,0 +1,261 @@ +#pragma once + +#include "wit-common.h" +#include +#include // unique_ptr +#include +#include +#include + +#ifndef WIT_HOST_DIRECT +#define WIT_HOST_WAMR +#endif + +// #ifdef WIT_HOST_DIRECT +// #define WIT_WASI64 +// #endif + +namespace wit { +#ifdef WIT_HOST_DIRECT +typedef uint8_t *guest_address; +typedef size_t guest_size; +extern "C" void *cabi_realloc(void *ptr, size_t old_size, size_t align, + size_t new_size); +#define INVALID_GUEST_ADDRESS nullptr +#elif defined(WIT_WASI64) +typedef uint64_t guest_address; +typedef uint64_t guest_size; +#define INVALID_GUEST_ADDRESS 0 +#else +typedef uint32_t guest_address; +typedef uint32_t guest_size; +#define INVALID_GUEST_ADDRESS 0 +#endif +} // namespace wit + +#ifdef WIT_HOST_WAMR +#include +#include +#endif + +namespace wit { +#ifdef WIT_HOST_WAMR +typedef void (*guest_cabi_post_t)(WASMExecEnv *, guest_address); +typedef guest_address (*guest_alloc_t)(WASMExecEnv *, guest_size size, + guest_size align); +#endif + +/// A string in linear memory (host-side handle) +// host code never de-allocates directly +class string { + guest_address data_; + guest_size length; + +public: +#ifdef WIT_HOST_WAMR + std::string_view get_view(WASMExecEnv *inst) const { + return std::string_view((char const *)wasm_runtime_addr_app_to_native( + wasm_runtime_get_module_inst(inst), data_), + length); + } +#elif defined(WIT_HOST_DIRECT) + std::string_view get_view() const { + return std::string_view((char const *)data_, length); + } +#endif + string(guest_address a, guest_size s) : data_(a), length(s) {} + guest_address data() const { return data_; } + guest_size size() const { return length; } + // add a convenient way to create a string + +#if defined(WIT_HOST_DIRECT) + static string from_view(std::string_view v) { + void *addr = cabi_realloc(nullptr, 0, 1, v.length()); + memcpy(addr, v.data(), v.length()); + return string((guest_address)addr, v.length()); + } +#endif +#if defined(WIT_HOST_WAMR) + static string from_view(wasm_exec_env_t exec_env, std::string_view v) { + void *addr = nullptr; + wasm_function_inst_t wasm_func = wasm_runtime_lookup_function( + wasm_runtime_get_module_inst(exec_env), "cabi_realloc"/*, "(*$ii)*"*/); + + wasm_val_t wasm_results[1] = {WASM_INIT_VAL}; + wasm_val_t wasm_args[4] = { + WASM_I32_VAL(0 /*nullptr*/), + WASM_I32_VAL(0), + WASM_I32_VAL(1), + WASM_I32_VAL(0 /*v.length()*/), + }; + bool wasm_ok; + wasm_args[3].of.i32 = v.length(); + wasm_ok = wasm_runtime_call_wasm_a(exec_env, wasm_func, 1, wasm_results, 4, + wasm_args); + assert(wasm_ok); + assert(wasm_results[0].kind == WASM_I32); + auto ret = wasm_results[0].of.i32; + addr = (void *)wasm_runtime_addr_app_to_native( + wasm_runtime_get_module_inst(exec_env), ret); + memcpy(addr, v.data(), v.length()); + return string((guest_address)ret, v.length()); + } +#endif +}; + +/// A vector in linear memory (host-side handle) +template class vector { + guest_address data_; + guest_size length; + +public: +#ifdef WIT_HOST_WAMR + std::string_view get_view(WASMExecEnv *inst) const { + return wit::span((T const *)wasm_runtime_addr_app_to_native( + wasm_runtime_get_module_inst(inst), data_), + length); + } +#elif defined(WIT_HOST_DIRECT) + std::string_view get_view() const { + return wit::span((T const *)data_, length); + } +#endif + vector(guest_address a, guest_size s) : data_(a), length(s) {} + guest_address data() const { return data_; } + guest_size size() const { return length; } +}; + +/// Wrapper for specialized de-allocation of a returned type (calling +/// cabi_post_*) +template class guest_owned : public T { + guest_address data_; +#ifdef WIT_HOST_WAMR + wasm_function_inst_t free_func; + WASMExecEnv *exec_env; +#elif defined(WIT_HOST_DIRECT) + void (*free_func)(guest_address); +#endif +public: + guest_owned(guest_owned const &) = delete; + guest_owned &operator=(guest_owned const &) = delete; + ~guest_owned() { + if (data_) { +#ifdef WIT_HOST_WAMR + wasm_val_t *wasm_results = nullptr; + wasm_val_t wasm_args[1] = { + WASM_I32_VAL((int32_t)data_), + }; + wasm_runtime_call_wasm_a(exec_env, free_func, 0, wasm_results, 1, + wasm_args); +#elif defined(WIT_HOST_DIRECT) + (*free_func)(data_); +#endif + } + } + guest_owned(guest_owned &&b) + : T(b), data_(b.data_), free_func(b.free_func) +#ifdef WIT_HOST_WAMR + , + exec_env(b.exec_env) +#endif + { + b.data_ = INVALID_GUEST_ADDRESS; + } + guest_owned(T &&t, guest_address a, +#ifdef WIT_HOST_WAMR + wasm_function_inst_t f, WASMExecEnv *e +#elif defined(WIT_HOST_DIRECT) + void (*f)(guest_address) +#endif + ) + : T(std::move(t)), data_(a), free_func(f) +#ifdef WIT_HOST_WAMR + , + exec_env(e) +#endif + { + } + T const &inner() const { return *static_cast(this); } + +#ifdef WIT_HOST_WAMR + // not necessary? as the only way to get a guest_owned object + // is to pass exec_env + // WASMExecEnv* get_exec_env() const { + // return exec_env; + // } +#endif +}; + +/// Guest exported resource (host side handle) +class ResourceExportBase : public ResourceTable { +protected: + guest_address rep; + int32_t index; + +public: + ResourceExportBase() : rep(INVALID_GUEST_ADDRESS), index(-1) {} + ResourceExportBase(int32_t i) : rep(*lookup_resource(i)), index(i) {} + ResourceExportBase(ResourceExportBase &&b) : rep(b.rep), index(b.index) { + b.rep = 0; + } + ResourceExportBase(ResourceExportBase const &) = delete; + ResourceExportBase &operator=(ResourceExportBase const &) = delete; + ResourceExportBase &operator=(ResourceExportBase &&b) { + assert(rep == 0); + rep = b.rep; + index = b.index; + b.rep = 0; + } + ~ResourceExportBase() { + if (index >= 0 && rep != INVALID_GUEST_ADDRESS) { + remove_resource(index); + } + } + int32_t get_handle() const { return index; } + guest_address get_rep() const { return rep; } + guest_address take_rep() { + guest_address res = rep; + rep = 0; + return res; + } +}; + +/// Host defined resource (host side definition) +template class ResourceImportBase : public ResourceTable { + int32_t index; + +public: + struct Deleter { + void operator()(R *ptr) const { R::Dtor(ptr); } + }; + typedef std::unique_ptr Owned; + + static const int32_t invalid = -1; + ResourceImportBase() : index(this->store_resource((R *)this)) {} + ~ResourceImportBase() {} + ResourceImportBase(ResourceImportBase &&b) = delete; + ResourceImportBase(ResourceImportBase const &) = delete; + ResourceImportBase &operator=(ResourceImportBase const &) = delete; + ResourceImportBase &operator=(ResourceImportBase &&) = delete; + int32_t get_handle() { return index; } +}; + +/// Host representation of a resource defined in another component +/// +/// Acts like ResourceImportBase (e.g. definition); +/// R should derive from ResourceExportBase +template class ResourceForwarder : public R { + typedef R Owned; + ResourceForwarder(int32_t id) : R(ResourceExportBase(id)) {} + std::optional lookup_resource(int32_t id) { + // TODO: Handle not found + return R(ResourceExportBase(id)); + } + std::optional remove_resource(int32_t id) { + std::optional result = R::remove_resource(id); + if (!result.has_value()) + return std::optional(); + return *result; + } +}; +} // namespace wit diff --git a/crates/cpp/src/lib.rs b/crates/cpp/src/lib.rs index 99a0fce6e..e3e1f9814 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -11,7 +11,7 @@ use symbol_name::{make_external_component, make_external_symbol}; use wit_bindgen_c::to_c_ident; use wit_bindgen_core::{ abi::{self, AbiVariant, Bindgen, Bitcast, LiftLower, WasmSignature, WasmType}, - uwrite, uwriteln, + symmetric, uwrite, uwriteln, wit_parser::{ Alignment, ArchitectureSize, Docs, Function, FunctionKind, Handle, Int, InterfaceId, Resolve, SizeAlign, Stability, Type, TypeDefKind, TypeId, TypeOwner, WorldId, WorldKey, @@ -19,8 +19,8 @@ use wit_bindgen_core::{ Files, InterfaceGenerator, Source, WorldGenerator, }; -// mod wamr; mod symbol_name; +mod wamr; pub const RESOURCE_IMPORT_BASE_CLASS_NAME: &str = "ResourceImportBase"; pub const RESOURCE_EXPORT_BASE_CLASS_NAME: &str = "ResourceExportBase"; @@ -74,6 +74,7 @@ struct Includes { needs_string_view: bool, needs_optional: bool, needs_cstring: bool, + needs_guest_alloc: bool, needs_imported_resources: bool, needs_exported_resources: bool, needs_variant: bool, @@ -82,6 +83,15 @@ struct Includes { // needs wit types needs_wit: bool, needs_memory: bool, + needs_future: bool, + needs_stream: bool, +} + +#[derive(Clone)] +struct HostFunction { + wasm_name: String, + wamr_signature: String, + host_name: String, } #[derive(Default)] @@ -105,6 +115,7 @@ struct Cpp { extern_c_decls: Source, dependencies: Includes, includes: Vec, + host_functions: HashMap>, world: String, world_id: Option, imported_interfaces: HashSet, @@ -114,6 +125,8 @@ struct Cpp { // needed for symmetric disambiguation interface_prefixes: HashMap<(Direction, WorldKey), String>, import_prefix: Option, + + temp: usize, } #[derive(Default, Debug, Clone, Copy)] @@ -172,9 +185,18 @@ impl core::fmt::Display for Ownership { #[derive(Default, Debug, Clone)] #[cfg_attr(feature = "clap", derive(clap::Args))] pub struct Opts { + /// Generate host bindings + #[cfg_attr(feature = "clap", arg(long, default_value_t = bool::default()))] + pub host: bool, + /// Generate code for directly linking to guest code (WIP) + #[cfg_attr(feature = "clap", arg(long, default_value_t = bool::default(), alias = "direct"))] + pub short_cut: bool, /// Call clang-format on the generated code #[cfg_attr(feature = "clap", arg(long, default_value_t = bool::default()))] pub format: bool, + /// 64bit guest + #[cfg_attr(feature = "clap", arg(long, default_value_t = bool::default()))] + pub wasm64: bool, /// Place each interface in its own file, /// this enables sharing bindings across projects @@ -209,6 +231,11 @@ pub struct Opts { #[cfg_attr(feature = "clap", arg(long, default_value_t = Ownership::Owning))] pub ownership: Ownership, + /// Symmetric ABI, this enables to directly link components to each + /// other and removes the primary distinction between host and guest. + #[cfg_attr(feature = "clap", arg(long, default_value_t = bool::default()))] + pub symmetric: bool, + /// Symmetric API, same API for imported and exported functions. /// Reduces the allocation overhead for symmetric ABI. #[cfg_attr( @@ -239,12 +266,30 @@ impl Opts { Box::new(r) } + fn host_side(&self) -> bool { + self.short_cut || self.host + } + fn is_only_handle(&self, variant: AbiVariant) -> bool { - !matches!(variant, AbiVariant::GuestExport) + self.host_side() == matches!(variant, AbiVariant::GuestExport) } fn ptr_type(&self) -> &'static str { - "uint8_t*" + if !self.host { + "uint8_t*" + } else if self.wasm64 { + "int64_t" + } else { + "int32_t" + } + } + + // we need to map pointers depending on context + fn wasm_type(&self, ty: WasmType) -> &'static str { + match ty { + WasmType::Pointer => self.ptr_type(), + _ => wit_bindgen_c::wasm_type(ty), + } } } @@ -274,7 +319,11 @@ impl Cpp { in_guest_import: bool, wasm_import_module: Option, ) -> CppInterfaceGenerator<'a> { - let mut sizes = SizeAlign::default(); + let mut sizes = if self.opts.symmetric { + SizeAlign::new_symmetric() + } else { + SizeAlign::default() + }; sizes.fill(resolve); CppInterfaceGenerator { @@ -361,6 +410,12 @@ impl Cpp { if self.dependencies.needs_expected { self.include(""); } + if self.dependencies.needs_future { + self.include(""); + } + if self.dependencies.needs_stream { + self.include(""); + } if self.dependencies.needs_optional { self.include(""); } @@ -380,7 +435,11 @@ impl Cpp { self.include(""); } if self.dependencies.needs_wit { - self.include(""); + if self.opts.host_side() { + self.include(""); + } else { + self.include(""); + } } if self.dependencies.needs_memory { self.include(""); @@ -405,6 +464,9 @@ impl Cpp { self.finish_includes(); self.h_src.change_namespace(&[]); uwriteln!(header, "#pragma once"); + if self.opts.symmetric { + uwriteln!(header, "#define WIT_SYMMETRIC"); + } for include in self.includes.iter() { uwriteln!(header, "#include {include}"); } @@ -422,6 +484,11 @@ impl Cpp { self.includes.push(String::from("\"") + &filename + "\""); } } + + fn tmp(&mut self) -> usize { + self.temp += 1; + self.temp + } } #[derive(Default)] @@ -436,14 +503,21 @@ impl WorldGenerator for Cpp { let name = &resolve.worlds[world].name; self.world = name.to_string(); self.world_id = Some(world); - uwriteln!( - self.c_src_head, - r#"#include "{}_cpp.h" + // self.sizes.fill(resolve); + if !self.opts.host_side() { + let export_name = if self.opts.symmetric { + "" + } else { + ", __export_name__(\"cabi_realloc\")" + }; + uwriteln!( + self.c_src_head, + r#"#include "{}_cpp.h" #include // realloc extern "C" void *cabi_realloc(void *ptr, size_t old_size, size_t align, size_t new_size); - __attribute__((__weak__, __export_name__("cabi_realloc"))) + __attribute__((__weak__{export_name})) void *cabi_realloc(void *ptr, size_t old_size, size_t align, size_t new_size) {{ (void) old_size; if (new_size == 0) return (void*) align; @@ -453,8 +527,9 @@ impl WorldGenerator for Cpp { }} "#, - self.world.to_snake_case(), - ); + self.world.to_snake_case(), + ); + } } fn import_interface( @@ -602,14 +677,39 @@ impl WorldGenerator for Cpp { "// Generated by `wit-bindgen` {version}. DO NOT EDIT!" ); - uwrite!( - h_str.src, - "#ifndef __CPP_GUEST_BINDINGS_{0}_H - #define __CPP_GUEST_BINDINGS_{0}_H\n", - world.name.to_shouty_snake_case(), - ); + if self.opts.short_cut { + uwrite!( + h_str.src, + "#ifndef __CPP_NATIVE_BINDINGS_{0}_H + #define __CPP_NATIVE_BINDINGS_{0}_H\n", + world.name.to_shouty_snake_case(), + ); + } else if !self.opts.host { + uwrite!( + h_str.src, + "#ifndef __CPP_GUEST_BINDINGS_{0}_H + #define __CPP_GUEST_BINDINGS_{0}_H\n", + world.name.to_shouty_snake_case(), + ); + } else { + uwrite!( + h_str.src, + "#ifndef __CPP_HOST_BINDINGS_{0}_H + #define __CPP_HOST_BINDINGS_{0}_H + struct WASMExecEnv; // WAMR execution environment\n", + world.name.to_shouty_snake_case(), + ); + } + if self.dependencies.needs_future || self.dependencies.needs_stream { + uwriteln!(self.c_src_head, "#include \"async_support.h\"") + } self.finish_includes(); + if self.opts.short_cut { + uwriteln!(h_str.src, "#define WIT_HOST_DIRECT"); + } else if self.opts.symmetric { + uwriteln!(h_str.src, "#define WIT_SYMMETRIC"); + } for include in self.includes.iter() { uwriteln!(h_str.src, "#include {include}"); } @@ -618,20 +718,48 @@ impl WorldGenerator for Cpp { c_str.src, "// Generated by `wit-bindgen` {version}. DO NOT EDIT!" ); - uwriteln!( - c_str.src, - "\n// Ensure that the *_component_type.o object is linked in" - ); - uwrite!( - c_str.src, - "#ifdef __wasm32__ + if self.opts.short_cut { + uwriteln!(c_str.src, "#include \"{snake}_cpp_native.h\""); + } else if !self.opts.host { + uwriteln!( + c_str.src, + "\n// Ensure that the *_component_type.o object is linked in" + ); + uwrite!( + c_str.src, + "#ifdef __wasm32__ extern void {linking_symbol}(void); void {linking_symbol}_public_use_in_this_compilation_unit(void) {{ {linking_symbol}(); }} #endif ", - ); + ); + } else { + uwriteln!(c_str.src, "#include \"{snake}_cpp_host.h\""); + uwriteln!( + c_str.src, + "#include // wasm-micro-runtime header\n\ + #include \n\ + #include " + ); + + if c_str.src.len() > 0 { + c_str.src.push_str("\n"); + } + if self.dependencies.needs_guest_alloc { + uwriteln!( + c_str.src, + "int32_t guest_alloc(wasm_exec_env_t exec_env, uint32_t size);" + ); + } + } + if self.opts.host_side() && self.dependencies.needs_exported_resources { + uwriteln!( + c_str.src, + "template std::map wit::{RESOURCE_TABLE_NAME}::resources;" + ); + } if self.dependencies.needs_assert { uwriteln!(c_str.src, "#include "); } @@ -647,6 +775,40 @@ impl WorldGenerator for Cpp { uwriteln!(c_str.src, "\n// Component Adapters"); + if !self.opts.short_cut && self.opts.host { + uwriteln!( + h_str.src, + "extern \"C\" void register_{}();", + world.name.to_snake_case() + ); + uwriteln!( + c_str.src, + "void register_{}() {{", + world.name.to_snake_case() + ); + for i in self.host_functions.iter() { + uwriteln!( + c_str.src, + " static NativeSymbol {}_funs[] = {{", + i.0.replace(&[':', '.', '-', '+'], "_").to_snake_case() + ); + for f in i.1.iter() { + uwriteln!( + c_str.src, + " {{ \"{}\", (void*){}, \"{}\", nullptr }},", + f.wasm_name, + f.host_name, + f.wamr_signature + ); + } + uwriteln!(c_str.src, " }};"); + } + for i in self.host_functions.iter() { + uwriteln!(c_str.src, " wasm_runtime_register_natives(\"{}\", {1}_funs, sizeof({1}_funs)/sizeof(NativeSymbol));", i.0, i.0.replace(&[':','.','-','+'], "_").to_snake_case()); + } + uwriteln!(c_str.src, "}}"); + } + uwriteln!( h_str.src, " @@ -654,12 +816,20 @@ impl WorldGenerator for Cpp { ); if self.opts.format { - Self::clang_format(c_str.src.as_mut_string()); - Self::clang_format(h_str.src.as_mut_string()); + Self::clang_format(&mut c_str.src.as_mut_string()); + Self::clang_format(&mut h_str.src.as_mut_string()); } - files.push(&format!("{snake}.cpp"), c_str.src.as_bytes()); - files.push(&format!("{snake}_cpp.h"), h_str.src.as_bytes()); + if self.opts.short_cut { + files.push(&format!("{snake}_native.cpp"), c_str.src.as_bytes()); + files.push(&format!("{snake}_cpp_native.h"), h_str.src.as_bytes()); + } else if !self.opts.host { + files.push(&format!("{snake}.cpp"), c_str.src.as_bytes()); + files.push(&format!("{snake}_cpp.h"), h_str.src.as_bytes()); + } else { + files.push(&format!("{snake}_host.cpp"), c_str.src.as_bytes()); + files.push(&format!("{snake}_cpp_host.h"), h_str.src.as_bytes()); + } for (name, content) in self.user_class_files.iter() { // if the user class file exists create an updated .template let dst = match &self.opts.out_dir { @@ -684,8 +854,39 @@ impl WorldGenerator for Cpp { .unwrap() .as_slice(), ); + if self.opts.symmetric { + // this keeps the symbols down for shared objects, could be more specific + files.push( + &format!("{}.verscr", world.name), + b"{\n global:\n *X00*;\n local: *;\n};\n", + ); + } Ok(()) } + + fn apply_resolve_options(&mut self, resolve: &mut Resolve, world: &mut WorldId) { + if self.opts.symmetric { + let world = &resolve.worlds[*world]; + let exports: HashMap<&WorldKey, &wit_bindgen_core::wit_parser::WorldItem> = + world.exports.iter().collect(); + for (key, _item) in world.imports.iter() { + // duplicate found + if exports.contains_key(key) + && !self + .interface_prefixes + .contains_key(&(Direction::Import, key.clone())) + && !self + .interface_prefixes + .contains_key(&(Direction::Export, key.clone())) + { + self.interface_prefixes + .insert((Direction::Import, key.clone()), "imp_".into()); + self.interface_prefixes + .insert((Direction::Export, key.clone()), "exp_".into()); + } + } + } + } } // determine namespace (for the lifted C++ function) @@ -844,13 +1045,21 @@ impl CppInterfaceGenerator<'_> { } else { match is_drop { SpecialMethod::ResourceDrop => { - if guest_export { + if self.gen.opts.host_side() && !guest_export { + "Dtor".to_string() + } else if guest_export { "ResourceDrop".to_string() } else { "~".to_string() + &object } } - SpecialMethod::Dtor => "Dtor".to_string(), + SpecialMethod::Dtor => { + if self.gen.opts.host_side() && guest_export { + "~".to_string() + &object + } else { + "Dtor".to_string() + } + } SpecialMethod::ResourceNew => "ResourceNew".to_string(), SpecialMethod::ResourceRep => "ResourceRep".to_string(), SpecialMethod::Allocate => "New".to_string(), @@ -863,10 +1072,34 @@ impl CppInterfaceGenerator<'_> { (namespace, func_name_h) } + // local patching of borrows function needs more complex solution + fn patched_wasm_signature(&self, variant: AbiVariant, func: &Function) -> WasmSignature { + abi::wasm_signature_symmetric(self.resolve, variant, func, self.gen.opts.symmetric) + // if matches!(res.params.get(0), Some(WasmType::I32)) + // && matches!(func.kind, FunctionKind::Freestanding) + // { + // if let Some((_, ty)) = func.params.get(0) { + // if let Type::Id(id) = ty { + // if let Some(td) = self.resolve.types.get(*id) { + // if let TypeDefKind::Handle(Handle::Borrow(id2)) = &td.kind { + // if let Some(ty2) = self.resolve.types.get(*id2) { + // dbg!((&self.gen.imported_interfaces, id2, ty2, &func)); + // } + // } + // } + // } + // } + // } + } + // print the signature of the guest export (lowered (wasm) function calling into highlevel) fn print_export_signature(&mut self, func: &Function, variant: AbiVariant) -> Vec { let is_drop = is_special_method(func); - let id_type = WasmType::I32; + let id_type = if self.gen.opts.symmetric { + WasmType::Pointer + } else { + WasmType::I32 + }; let signature = match is_drop { SpecialMethod::ResourceDrop => WasmSignature { params: vec![id_type], @@ -894,7 +1127,7 @@ impl CppInterfaceGenerator<'_> { }, SpecialMethod::None => { // TODO perhaps remember better names for the arguments - self.resolve.wasm_signature(variant, func) + self.patched_wasm_signature(variant, func) } SpecialMethod::Allocate => WasmSignature { params: vec![], @@ -904,7 +1137,11 @@ impl CppInterfaceGenerator<'_> { }, }; let mut module_name = self.wasm_import_module.clone(); - let symbol_variant = variant; + let mut symbol_variant = variant; + if self.gen.opts.symmetric && matches!(variant, AbiVariant::GuestExport) { + // symmetric doesn't distinguish + symbol_variant = AbiVariant::GuestImport; + } if matches!(variant, AbiVariant::GuestExport) && matches!( is_drop, @@ -914,29 +1151,47 @@ impl CppInterfaceGenerator<'_> { ) { module_name = Some(String::from("[export]") + &module_name.unwrap()); + if self.gen.opts.host_side() { + symbol_variant = AbiVariant::GuestImport; + } } - let func_name = func.name.clone(); - let module_prefix = module_name.as_ref().map_or(String::default(), |name| { - let mut res = name.clone(); - res.push('#'); - res - }); - uwriteln!( - self.gen.c_src.src, - r#"extern "C" __attribute__((__export_name__("{module_prefix}{func_name}")))"# - ); - let return_via_pointer = false; + let func_name = if self.gen.opts.symmetric && matches!(is_drop, SpecialMethod::Dtor) { + // replace [dtor] with [resource_drop] + format!("[resource_drop]{}", &func.name[6..]) + } else { + func.name.clone() + }; + if self.gen.opts.short_cut { + uwrite!(self.gen.c_src.src, "extern \"C\" "); + } else if self.gen.opts.host { + self.gen.c_src.src.push_str("static "); + } else { + let module_prefix = module_name.as_ref().map_or(String::default(), |name| { + let mut res = name.clone(); + res.push('#'); + res + }); + if self.gen.opts.symmetric { + uwriteln!(self.gen.c_src.src, r#"extern "C" "#); + } else { + uwriteln!( + self.gen.c_src.src, + r#"extern "C" __attribute__((__export_name__("{module_prefix}{func_name}")))"# + ); + } + } + let return_via_pointer = signature.retptr && self.gen.opts.host_side(); self.gen .c_src .src .push_str(if signature.results.is_empty() || return_via_pointer { "void" } else { - wit_bindgen_c::wasm_type(signature.results[0]) + self.gen.opts.wasm_type(signature.results[0]) }); self.gen.c_src.src.push_str(" "); let export_name = match module_name { - Some(ref module_name) => make_external_symbol(module_name, &func_name, symbol_variant), + Some(ref module_name) => make_external_symbol(&module_name, &func_name, symbol_variant), None => make_external_component(&func_name), }; if let Some(prefix) = self.gen.opts.export_prefix.as_ref() { @@ -945,6 +1200,10 @@ impl CppInterfaceGenerator<'_> { self.gen.c_src.src.push_str(&export_name); self.gen.c_src.src.push_str("("); let mut first_arg = true; + if self.gen.opts.host { + self.gen.c_src.src.push_str("wasm_exec_env_t exec_env"); + first_arg = false; + } let mut params = Vec::new(); for (n, ty) in signature.params.iter().enumerate() { let name = format!("arg{n}"); @@ -953,7 +1212,7 @@ impl CppInterfaceGenerator<'_> { } else { first_arg = false; } - self.gen.c_src.src.push_str(wit_bindgen_c::wasm_type(*ty)); + self.gen.c_src.src.push_str(self.gen.opts.wasm_type(*ty)); self.gen.c_src.src.push_str(" "); self.gen.c_src.src.push_str(&name); params.push(name); @@ -967,6 +1226,19 @@ impl CppInterfaceGenerator<'_> { params.push("resultptr".into()); } self.gen.c_src.src.push_str(")\n"); + if self.gen.opts.host_side() { + let signature = wamr::wamr_signature(self.resolve, func); + let remember = HostFunction { + wasm_name: func_name.clone(), + wamr_signature: signature.to_string(), + host_name: export_name.clone(), + }; + self.gen + .host_functions + .entry(module_name.unwrap_or(self.gen.world.clone())) + .and_modify(|v| v.push(remember.clone())) + .or_insert(vec![remember]); + } params } @@ -985,14 +1257,22 @@ impl CppInterfaceGenerator<'_> { let is_drop = is_special_method(func); // we might want to separate c_sig and h_sig // let mut sig = String::new(); + if self.gen.opts.symmetric && matches!(is_drop, SpecialMethod::ResourceNew) { + res.result = "uint8_t*".into(); + } else // not for ctor nor imported dtor on guest if !matches!(&func.kind, FunctionKind::Constructor(_)) && !(matches!(is_drop, SpecialMethod::ResourceDrop) - && matches!(abi_variant, AbiVariant::GuestImport)) + && matches!(abi_variant, AbiVariant::GuestImport) + && !self.gen.opts.host_side()) + && !(matches!(is_drop, SpecialMethod::Dtor) + && matches!(abi_variant, AbiVariant::GuestExport) + && self.gen.opts.host_side()) { if matches!(is_drop, SpecialMethod::Allocate) { res.result.push_str("Owned"); - } else if let Some(ty) = &func.result { + } + if let Some(ty) = &func.result { res.result.push_str( &(self.type_name(ty, outer_namespace, Flavor::Result(abi_variant)) + if matches!(is_drop, SpecialMethod::ResourceRep) { @@ -1012,7 +1292,11 @@ impl CppInterfaceGenerator<'_> { } if matches!(func.kind, FunctionKind::Static(_)) && !(matches!(&is_drop, SpecialMethod::ResourceDrop) - && matches!(abi_variant, AbiVariant::GuestImport)) + && matches!(abi_variant, AbiVariant::GuestImport) + && !self.gen.opts.host_side()) + && !(matches!(&is_drop, SpecialMethod::Dtor) + && matches!(abi_variant, AbiVariant::GuestExport) + && self.gen.opts.host_side()) { res.static_member = true; } @@ -1021,27 +1305,43 @@ impl CppInterfaceGenerator<'_> { && name == "self" && (matches!(&func.kind, FunctionKind::Method(_)) || (matches!(&is_drop, SpecialMethod::ResourceDrop) - && matches!(abi_variant, AbiVariant::GuestImport))) + && matches!(abi_variant, AbiVariant::GuestImport) + && !self.gen.opts.host_side()) + || (matches!(&is_drop, SpecialMethod::Dtor) + && matches!(abi_variant, AbiVariant::GuestExport) + && self.gen.opts.host_side())) { res.implicit_self = true; continue; } - let is_pointer = if i == 0 - && name == "self" - && matches!(&is_drop, SpecialMethod::Dtor | SpecialMethod::ResourceNew) - && matches!(abi_variant, AbiVariant::GuestExport) + if self.gen.opts.symmetric + && matches!( + &is_drop, + SpecialMethod::ResourceRep | SpecialMethod::ResourceDrop + ) { - "*" + res.arguments + .push((name.to_snake_case(), "uint8_t*".into())); } else { - "" - }; - res.arguments.push(( - name.to_snake_case(), - self.type_name(param, &res.namespace, Flavor::Argument(abi_variant)) + is_pointer, - )); + let is_pointer = if matches!( + (&is_drop, self.gen.opts.host_side()), + (SpecialMethod::Dtor, _) + | (SpecialMethod::ResourceNew, _) + | (SpecialMethod::ResourceDrop, true) + ) { + "*" + } else { + "" + }; + res.arguments.push(( + name.to_snake_case(), + self.type_name(param, &res.namespace, Flavor::Argument(abi_variant)) + + is_pointer, + )); + } } // default to non-const when exporting a method - let import = matches!(abi_variant, AbiVariant::GuestImport); + let import = matches!(abi_variant, AbiVariant::GuestImport) ^ self.gen.opts.host_side(); if matches!(func.kind, FunctionKind::Method(_)) && import { res.const_member = true; } @@ -1055,68 +1355,102 @@ impl CppInterfaceGenerator<'_> { import: bool, ) -> Vec { let is_special = is_special_method(func); - let from_namespace = self.gen.h_src.namespace.clone(); - let cpp_sig = self.high_level_signature(func, variant, &from_namespace); - if cpp_sig.static_member { - self.gen.h_src.src.push_str("static "); - } - self.gen.h_src.src.push_str(&cpp_sig.result); - if !cpp_sig.result.is_empty() { - self.gen.h_src.src.push_str(" "); - } - self.gen.h_src.src.push_str(&cpp_sig.name); - self.gen.h_src.src.push_str("("); - for (num, (arg, typ)) in cpp_sig.arguments.iter().enumerate() { - if num > 0 { - self.gen.h_src.src.push_str(", "); - } - self.gen.h_src.src.push_str(typ); - self.gen.h_src.src.push_str(" "); - self.gen.h_src.src.push_str(arg); - } - self.gen.h_src.src.push_str(")"); - if cpp_sig.const_member { - self.gen.h_src.src.push_str(" const"); - } - match (&is_special, false, &variant) { - (SpecialMethod::Allocate, _, _) => { - uwriteln!( - self.gen.h_src.src, - "{{\ + if !(import == true + && self.gen.opts.host_side() + && matches!( + &is_special, + SpecialMethod::ResourceDrop + | SpecialMethod::ResourceNew + | SpecialMethod::ResourceRep + )) + { + let from_namespace = self.gen.h_src.namespace.clone(); + let cpp_sig = self.high_level_signature(func, variant, &from_namespace); + if cpp_sig.static_member { + self.gen.h_src.src.push_str("static "); + } + if cpp_sig.post_return && self.gen.opts.host_side() { + self.gen.h_src.src.push_str("wit::guest_owned<"); + } + self.gen.h_src.src.push_str(&cpp_sig.result); + if cpp_sig.post_return && self.gen.opts.host_side() { + self.gen.h_src.src.push_str(">"); + } + if !cpp_sig.result.is_empty() { + self.gen.h_src.src.push_str(" "); + } + self.gen.h_src.src.push_str(&cpp_sig.name); + self.gen.h_src.src.push_str("("); + if + /*import &&*/ + self.gen.opts.host && !matches!(func.kind, FunctionKind::Method(_)) { + self.gen.h_src.src.push_str("WASMExecEnv* exec_env"); + if !cpp_sig.arguments.is_empty() { + self.gen.h_src.src.push_str(", "); + } + } + for (num, (arg, typ)) in cpp_sig.arguments.iter().enumerate() { + if num > 0 { + self.gen.h_src.src.push_str(", "); + } + self.gen.h_src.src.push_str(typ); + self.gen.h_src.src.push_str(" "); + self.gen.h_src.src.push_str(arg); + } + self.gen.h_src.src.push_str(")"); + if cpp_sig.const_member { + self.gen.h_src.src.push_str(" const"); + } + match (&is_special, self.gen.opts.host_side(), &variant) { + (SpecialMethod::Allocate, _, _) => { + uwrite!( + self.gen.h_src.src, + "{{\ return {OWNED_CLASS_NAME}(new {}({}));\ }}", - cpp_sig.namespace.last().unwrap(), //join("::"), - cpp_sig - .arguments - .iter() - .map(|(arg, _)| arg.clone()) - .collect::>() - .join(", ") - ); - // body is inside the header - return Vec::default(); - } - (SpecialMethod::Dtor, _, _ /*AbiVariant::GuestImport*/) - | (SpecialMethod::ResourceDrop, true, _) => { - uwriteln!( - self.gen.h_src.src, - "{{\ + cpp_sig.namespace.last().unwrap(), + cpp_sig + .arguments + .iter() + .map(|(arg, _)| arg.clone()) + .collect::>() + .join(", ") + ); + // body is inside the header + return Vec::default(); + } + (SpecialMethod::Dtor, _, AbiVariant::GuestImport) + | (SpecialMethod::ResourceDrop, true, _) => { + uwrite!( + self.gen.h_src.src, + "{{\ delete {};\ }}", - cpp_sig.arguments.first().unwrap().0 - ); + cpp_sig.arguments.get(0).unwrap().0 + ); + } + _ => self.gen.h_src.src.push_str(";\n"), } - _ => self.gen.h_src.src.push_str(";\n"), } + // drop(cpp_sig); // we want to separate the lowered signature (wasm) and the high level signature - if !import - && !matches!( - &is_special, - SpecialMethod::ResourceDrop - | SpecialMethod::ResourceNew - | SpecialMethod::ResourceRep - ) + if (!import + && (self.gen.opts.host_side() + || !matches!( + &is_special, + SpecialMethod::ResourceDrop + | SpecialMethod::ResourceNew + | SpecialMethod::ResourceRep + ))) + || (import + && self.gen.opts.host_side() + && matches!( + &is_special, + SpecialMethod::ResourceDrop + | SpecialMethod::ResourceNew + | SpecialMethod::ResourceRep + )) { self.print_export_signature(func, variant) } else { @@ -1124,13 +1458,25 @@ impl CppInterfaceGenerator<'_> { let c_namespace = self.gen.c_src.namespace.clone(); let cpp_sig = self.high_level_signature(func, variant, &c_namespace); let mut params = Vec::new(); + if cpp_sig.post_return && self.gen.opts.host_side() { + self.gen.c_src.src.push_str("wit::guest_owned<"); + } self.gen.c_src.src.push_str(&cpp_sig.result); + if cpp_sig.post_return && self.gen.opts.host_side() { + self.gen.c_src.src.push_str(">"); + } if !cpp_sig.result.is_empty() { self.gen.c_src.src.push_str(" "); } self.gen.c_src.qualify(&cpp_sig.namespace); self.gen.c_src.src.push_str(&cpp_sig.name); self.gen.c_src.src.push_str("("); + if import && self.gen.opts.host && !matches!(func.kind, FunctionKind::Method(_)) { + self.gen.c_src.src.push_str("wasm_exec_env_t exec_env"); + if !cpp_sig.arguments.is_empty() || cpp_sig.implicit_self { + self.gen.c_src.src.push_str(", "); + } + } if cpp_sig.implicit_self { params.push("(*this)".into()); } @@ -1173,8 +1519,8 @@ impl CppInterfaceGenerator<'_> { } let export = match variant { - AbiVariant::GuestImport => false, - AbiVariant::GuestExport => true, + AbiVariant::GuestImport => self.gen.opts.host_side(), + AbiVariant::GuestExport => !self.gen.opts.host_side(), AbiVariant::GuestImportAsync => todo!(), AbiVariant::GuestExportAsync => todo!(), AbiVariant::GuestExportAsyncStackful => todo!(), @@ -1183,18 +1529,25 @@ impl CppInterfaceGenerator<'_> { let special = is_special_method(func); if !matches!(special, SpecialMethod::Allocate) { self.gen.c_src.src.push_str("{\n"); - let needs_dealloc = - if self.gen.opts.new_api && matches!(variant, AbiVariant::GuestExport) { - self.gen - .c_src - .src - .push_str("std::vector _deallocate;\n"); - self.gen.dependencies.needs_vector = true; - true - } else { - false - }; - let lift_lower = if export { + let needs_dealloc = if self.gen.opts.new_api + && matches!(variant, AbiVariant::GuestExport) + && ((!self.gen.opts.symmetric + && symmetric::needs_dealloc(self.resolve, &func.params)) + || (self.gen.opts.symmetric + && symmetric::has_non_canonical_list(self.resolve, &func.params))) + { + self.gen + .c_src + .src + .push_str("std::vector _deallocate;\n"); + self.gen.dependencies.needs_vector = true; + true + } else { + false + }; + let lift_lower = if self.gen.opts.symmetric { + LiftLower::Symmetric + } else if export { LiftLower::LiftArgsLowerResults } else { LiftLower::LowerArgsLiftResults @@ -1202,65 +1555,177 @@ impl CppInterfaceGenerator<'_> { match is_special_method(func) { SpecialMethod::ResourceDrop => match lift_lower { LiftLower::LiftArgsLowerResults => { - let module_name = - String::from("[export]") + &self.wasm_import_module.clone().unwrap(); - let wasm_sig = - self.declare_import(&module_name, &func.name, &[WasmType::I32], &[]); - uwriteln!( - self.gen.c_src.src, - "{wasm_sig}({});", - func.params.first().unwrap().0 - ); + if self.gen.opts.host_side() { + let namespace = class_namespace(self, func, variant); + uwrite!(self.gen.c_src.src, " auto ptr = "); + self.gen.c_src.qualify(&namespace); + uwriteln!( + self.gen.c_src.src, + "remove_resource({}); + assert(ptr.has_value());", + params[0] + ); + self.gen.dependencies.needs_assert = true; + self.gen.c_src.qualify(&namespace); + uwriteln!(self.gen.c_src.src, "Dtor(*ptr);") + } else { + let module_name = String::from("[export]") + + &self.wasm_import_module.as_ref().map(|e| e.clone()).unwrap(); + let wasm_sig = self.declare_import( + &module_name, + &func.name, + &[WasmType::I32], + &[], + ); + uwriteln!( + self.gen.c_src.src, + "{wasm_sig}({});", + func.params.get(0).unwrap().0 + ); + } } LiftLower::LowerArgsLiftResults => { - let module_name = self.wasm_import_module.clone().unwrap(); - let name = - self.declare_import(&module_name, &func.name, &[WasmType::I32], &[]); - uwriteln!( - self.gen.c_src.src, - " if (handle>=0) {{ + if self.gen.opts.host_side() { + let namespace = class_namespace(self, func, variant); + self.gen.c_src.qualify(&namespace); + uwriteln!(self.gen.c_src.src, "remove_resource(arg0);"); + } else { + let module_name = + self.wasm_import_module.as_ref().map(|e| e.clone()).unwrap(); + let name = self.declare_import( + &module_name, + &func.name, + &[WasmType::I32], + &[], + ); + uwriteln!( + self.gen.c_src.src, + " if (handle>=0) {{ {name}(handle); }}" - ); + ); + } + } + LiftLower::Symmetric => { + let module_name = + self.wasm_import_module.as_ref().map(|e| e.clone()).unwrap(); + if matches!(variant, AbiVariant::GuestExport) { + let mut namespace = class_namespace(self, func, variant); + self.gen.c_src.qualify(&namespace); + self.gen.c_src.src.push_str("Dtor(("); + let classname = namespace.pop().unwrap_or_default(); + self.gen.c_src.qualify(&namespace); + uwriteln!( + self.gen.c_src.src, + "{classname}*){});", + func.params.get(0).unwrap().0 + ); + } else { + let name = self.declare_import( + &module_name, + &func.name, + &[WasmType::Pointer], + &[], + ); + uwriteln!( + self.gen.c_src.src, + " if (handle!=nullptr) {{ + {name}(handle); + }}" + ); + } } }, SpecialMethod::Dtor => { - let classname = class_namespace(self, func, variant).join("::"); - uwriteln!(self.gen.c_src.src, "(({classname}*)arg0)->handle=-1;"); - uwriteln!(self.gen.c_src.src, "{0}::Dtor(({0}*)arg0);", classname); + if self.gen.opts.host_side() { + let module_name = + self.wasm_import_module.as_ref().map(|e| e.clone()).unwrap(); + let name = self.declare_import( + &module_name, + &func.name, + &[WasmType::Pointer], + &[], + ); + uwriteln!( + self.gen.c_src.src, + "if (this->rep) {{ {name}(this->rep); }}" + ); + } else { + let classname = class_namespace(self, func, variant).join("::"); + if self.gen.opts.symmetric { + uwriteln!( + self.gen.c_src.src, + "{}::ResourceDrop(({})arg0);", + classname, + self.gen.opts.ptr_type() + ); + } else { + uwriteln!(self.gen.c_src.src, "(({classname}*)arg0)->handle=-1;"); + uwriteln!(self.gen.c_src.src, "{0}::Dtor(({0}*)arg0);", classname); + } + } } SpecialMethod::ResourceNew => { - let module_name = - String::from("[export]") + &self.wasm_import_module.clone().unwrap(); - let wasm_sig = self.declare_import( - &module_name, - &func.name, - &[WasmType::Pointer], - &[WasmType::I32], - ); - uwriteln!( - self.gen.c_src.src, - "return {wasm_sig}(({}){});", - self.gen.opts.ptr_type(), - func.params.first().unwrap().0 - ); + if self.gen.opts.symmetric { + uwriteln!( + self.gen.c_src.src, + "return ({}){};", + self.gen.opts.ptr_type(), + func.params.get(0).unwrap().0 + ); + } else if !self.gen.opts.host_side() { + let module_name = String::from("[export]") + + &self.wasm_import_module.as_ref().map(|e| e.clone()).unwrap(); + let wasm_sig = self.declare_import( + &module_name, + &func.name, + &[WasmType::Pointer], + &[WasmType::I32], + ); + uwriteln!( + self.gen.c_src.src, + "return {wasm_sig}(({}){});", + self.gen.opts.ptr_type(), + func.params.get(0).unwrap().0 + ); + } else { + uwriteln!(self.gen.c_src.src, "return "); + let namespace = class_namespace(self, func, variant); + self.gen.c_src.qualify(&namespace); + uwriteln!(self.gen.c_src.src, "store_resource(std::move(arg0));"); + } } SpecialMethod::ResourceRep => { - let module_name = - String::from("[export]") + &self.wasm_import_module.clone().unwrap(); - let wasm_sig = self.declare_import( - &module_name, - &func.name, - &[WasmType::I32], - &[WasmType::Pointer], - ); - let classname = class_namespace(self, func, variant).join("::"); - uwriteln!( - self.gen.c_src.src, - "return ({}*){wasm_sig}({});", - classname, - func.params.first().unwrap().0 - ); + if self.gen.opts.symmetric { + let classname = class_namespace(self, func, variant).join("::"); + uwriteln!( + self.gen.c_src.src, + "return ({}*){};", + classname, + func.params.get(0).unwrap().0 + ); + } else if !self.gen.opts.host_side() { + let module_name = String::from("[export]") + + &self.wasm_import_module.as_ref().map(|e| e.clone()).unwrap(); + let wasm_sig = self.declare_import( + &module_name, + &func.name, + &[WasmType::I32], + &[WasmType::Pointer], + ); + let classname = class_namespace(self, func, variant).join("::"); + uwriteln!( + self.gen.c_src.src, + "return ({}*){wasm_sig}({});", + classname, + func.params.get(0).unwrap().0 + ); + } else { + uwriteln!(self.gen.c_src.src, "return *"); + let namespace = class_namespace(self, func, variant); + self.gen.c_src.qualify(&namespace); + uwriteln!(self.gen.c_src.src, "lookup_resource(arg0);",); + } } SpecialMethod::Allocate => unreachable!(), SpecialMethod::None => { @@ -1295,10 +1760,29 @@ impl CppInterfaceGenerator<'_> { let mut f = FunctionBindgen::new(self, params); if !export { f.namespace = namespace.clone(); + f.wamr_signature = Some(wamr::wamr_signature(&f.gen.resolve, func)); } f.variant = variant; f.needs_dealloc = needs_dealloc; - f.cabi_post = None; + f.cabi_post = if matches!(variant, AbiVariant::GuestExport) + && f.gen.gen.opts.host_side() + && abi::guest_export_needs_post_return(f.gen.resolve, func) + { + let module_name = f + .gen + .wasm_import_module + .as_ref() + .map(|e| e.clone()) + .unwrap(); + let cpp_sig = f.gen.high_level_signature(func, variant, &namespace); + Some(CabiPostInformation { + module: module_name, + name: func.name.clone(), + ret_type: cpp_sig.result, + }) + } else { + None + }; abi::call(f.gen.resolve, variant, lift_lower, func, &mut f, false); let code = String::from(f.src); self.gen.c_src.src.push_str(&code); @@ -1306,11 +1790,13 @@ impl CppInterfaceGenerator<'_> { } self.gen.c_src.src.push_str("}\n"); // cabi_post - if matches!(variant, AbiVariant::GuestExport) + if !self.gen.opts.host_side() + && !matches!(lift_lower, LiftLower::Symmetric) + && matches!(variant, AbiVariant::GuestExport) && abi::guest_export_needs_post_return(self.resolve, func) { - let sig = self.resolve.wasm_signature(variant, func); - let module_name = self.wasm_import_module.clone(); + let sig = self.patched_wasm_signature(variant, func); + let module_name = self.wasm_import_module.as_ref().map(|e| e.clone()); let export_name = match module_name { Some(ref module_name) => { format!("{module_name}#{}", func.name) @@ -1318,9 +1804,15 @@ impl CppInterfaceGenerator<'_> { None => make_external_component(&func.name), }; let import_name = match module_name { - Some(ref module_name) => { - make_external_symbol(module_name, &func.name, AbiVariant::GuestExport) - } + Some(ref module_name) => make_external_symbol( + module_name, + &func.name, + if self.gen.opts.symmetric { + AbiVariant::GuestImport + } else { + AbiVariant::GuestExport + }, + ), None => make_external_component(&func.name), }; uwriteln!( @@ -1335,10 +1827,19 @@ impl CppInterfaceGenerator<'_> { uwrite!( self.gen.c_src.src, "{} {name}", - wit_bindgen_c::wasm_type(*result) + self.gen.opts.wasm_type(*result) ); params.push(name); } + if sig.retptr && self.gen.opts.symmetric { + let name = "retptr"; + uwrite!( + self.gen.c_src.src, + "{} {name}", + self.gen.opts.wasm_type(WasmType::Pointer) + ); + params.push(name.into()); + } self.gen.c_src.src.push_str(") {\n"); let mut f = FunctionBindgen::new(self, params.clone()); @@ -1351,6 +1852,50 @@ impl CppInterfaceGenerator<'_> { } } + // pub fn type_path(&self, id: TypeId, owned: bool) -> String { + // self.type_path_with_name( + // id, + // if owned { + // self.result_name(id) + // } else { + // self.param_name(id) + // }, + // ) + // } + + // fn type_path_with_name(&self, id: TypeId, name: String) -> String { + // if let TypeOwner::Interface(id) = self.resolve.types[id].owner { + // if let Some(path) = self.path_to_interface(id) { + // return format!("{path}::{name}"); + // } + // } + // name + // } + + // fn path_to_interface(&self, interface: InterfaceId) -> Option { + // let iface = &self.resolve.interfaces[interface]; + // let name = iface.name.as_ref().unwrap(); + // let mut full_path = String::new(); + // full_path.push_str(name); + // Some(full_path) + // } + + // fn param_name(&self, ty: TypeId) -> String { + // self.resolve.types[ty] + // .name + // .as_ref() + // .unwrap() + // .to_upper_camel_case() + // } + + // fn result_name(&self, ty: TypeId) -> String { + // self.resolve.types[ty] + // .name + // .as_ref() + // .unwrap() + // .to_upper_camel_case() + // } + // in C this is print_optional_ty fn optional_type_name( &mut self, @@ -1407,10 +1952,14 @@ impl CppInterfaceGenerator<'_> { self.gen.dependencies.needs_string_view = true; "std::string_view".into() } - Flavor::Argument(AbiVariant::GuestExport) => { + Flavor::Argument(AbiVariant::GuestExport) if !self.gen.opts.host_side() => { self.gen.dependencies.needs_wit = true; "wit::string &&".into() } + Flavor::Result(AbiVariant::GuestExport) if self.gen.opts.host_side() => { + self.gen.dependencies.needs_string_view = true; + "std::string_view".into() + } _ => { self.gen.dependencies.needs_wit = true; "wit::string".into() @@ -1425,7 +1974,7 @@ impl CppInterfaceGenerator<'_> { } TypeDefKind::Handle(Handle::Own(id)) => { let mut typename = self.type_name(&Type::Id(*id), from_namespace, flavor); - match (false, flavor) { + match (self.gen.opts.host_side(), flavor) { (false, Flavor::Argument(AbiVariant::GuestImport)) | (true, Flavor::Argument(AbiVariant::GuestExport)) => { typename.push_str("&&") @@ -1496,18 +2045,32 @@ impl CppInterfaceGenerator<'_> { self.gen.dependencies.needs_wit = true; format!("wit::span<{inner} const>") } - Flavor::Argument(AbiVariant::GuestExport) => { + Flavor::Argument(AbiVariant::GuestExport) if !self.gen.opts.host => { self.gen.dependencies.needs_wit = true; format!("wit::vector<{inner}>&&") } + Flavor::Result(AbiVariant::GuestExport) if self.gen.opts.host => { + self.gen.dependencies.needs_wit = true; + format!("wit::span<{inner} const>") + } _ => { self.gen.dependencies.needs_wit = true; format!("wit::vector<{inner}>") } } } - TypeDefKind::Future(_) => todo!(), - TypeDefKind::Stream(_) => todo!(), + TypeDefKind::Future(ty) => { + self.gen.dependencies.needs_future = true; + "std::future<".to_string() + + &self.optional_type_name(ty.as_ref(), from_namespace, flavor) + + ">" + } + TypeDefKind::Stream(ty) => { + self.gen.dependencies.needs_stream = true; + "wit::stream<".to_string() + + &self.optional_type_name(ty.as_ref(), from_namespace, flavor) + + ">" + } TypeDefKind::Type(ty) => self.type_name(ty, from_namespace, flavor), TypeDefKind::FixedSizeList(_, _) => todo!(), TypeDefKind::Unknown => todo!(), @@ -1525,8 +2088,11 @@ impl CppInterfaceGenerator<'_> { variant: AbiVariant, ) -> (String, String) { let extern_name = make_external_symbol(module_name, name, variant); - let import = format!("extern \"C\" __attribute__((import_module(\"{module_name}\")))\n __attribute__((import_name(\"{name}\")))\n {result} {extern_name}({args});\n") - ; + let import = if self.gen.opts.symmetric { + format!("extern \"C\" {result} {extern_name}({args});\n") + } else { + format!("extern \"C\" __attribute__((import_module(\"{module_name}\")))\n __attribute__((import_name(\"{name}\")))\n {result} {extern_name}({args});\n") + }; (extern_name, import) } @@ -1539,7 +2105,7 @@ impl CppInterfaceGenerator<'_> { ) -> String { let mut args = String::default(); for (n, param) in params.iter().enumerate() { - args.push_str(wit_bindgen_c::wasm_type(*param)); + args.push_str(self.gen.opts.wasm_type(*param)); if n + 1 != params.len() { args.push_str(", "); } @@ -1547,9 +2113,13 @@ impl CppInterfaceGenerator<'_> { let result = if results.is_empty() { "void" } else { - wit_bindgen_c::wasm_type(results[0]) + self.gen.opts.wasm_type(results[0]) + }; + let variant = if self.gen.opts.short_cut { + AbiVariant::GuestExport + } else { + AbiVariant::GuestImport }; - let variant = AbiVariant::GuestImport; let (name, code) = self.declare_import2(module_name, name, &args, result, variant); self.gen.extern_c_decls.push_str(&code); name @@ -1609,7 +2179,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for CppInterfaceGenerator<'a> let type_ = &self.resolve.types[id]; if let TypeOwner::Interface(intf) = type_.owner { let guest_import = self.gen.imported_interfaces.contains(&intf); - let definition = !(guest_import); + let definition = !(guest_import ^ self.gen.opts.host_side()); let store = self.gen.start_new_file(Some(definition)); let mut world_name = self.gen.world.to_snake_case(); world_name.push_str("::"); @@ -1634,7 +2204,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for CppInterfaceGenerator<'a> } self.gen.dependencies.needs_wit = true; - let base_type = match (definition, false) { + let base_type = match (definition, self.gen.opts.host_side()) { (true, false) => format!("wit::{RESOURCE_EXPORT_BASE_CLASS_NAME}<{pascal}>"), (false, false) => { String::from_str("wit::").unwrap() + RESOURCE_IMPORT_BASE_CLASS_NAME @@ -1686,7 +2256,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for CppInterfaceGenerator<'a> } { self.generate_function(func, &TypeOwner::Interface(intf), variant); if matches!(func.kind, FunctionKind::Constructor(_)) - && matches!(variant, AbiVariant::GuestExport) + && matches!(variant, AbiVariant::GuestExport) != self.gen.opts.host_side() { // functional safety requires the option to use a different allocator, so move new into the implementation let func2 = Function { @@ -1711,6 +2281,11 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for CppInterfaceGenerator<'a> self.gen.h_src.src, "{pascal}& operator=({pascal}&&) = default;" ); + uwriteln!(self.gen.h_src.src, "{pascal}(const {pascal}&) = delete;"); + uwriteln!( + self.gen.h_src.src, + "{pascal}& operator=(const {pascal}&) = delete;" + ); self.gen.c_src.qualify(&namespc); uwriteln!( self.gen.c_src.src, @@ -1718,7 +2293,11 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for CppInterfaceGenerator<'a> ); } if matches!(variant, AbiVariant::GuestExport) { - let id_type = Type::S32; + let id_type = if self.gen.opts.symmetric { + Type::Id(id) + } else { + Type::S32 + }; let func = Function { name: "[resource-new]".to_string() + name, kind: FunctionKind::Static(id), @@ -1960,6 +2539,7 @@ struct FunctionBindgen<'a, 'b> { blocks: Vec<(String, Vec)>, payloads: Vec, // caching for wasm + wamr_signature: Option, variant: AbiVariant, cabi_post: Option, needs_dealloc: bool, @@ -1977,6 +2557,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { block_storage: Default::default(), blocks: Default::default(), payloads: Default::default(), + wamr_signature: None, variant: AbiVariant::GuestImport, cabi_post: None, needs_dealloc: false, @@ -1998,6 +2579,10 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { self.src.push_str(s); } + // fn typename_lift(&self, id: TypeId) -> String { + // self.gen.type_path(id, true) + // } + fn let_results(&mut self, amt: usize, results: &mut Vec) { if amt > 0 { let tmp = self.tmp(); @@ -2022,12 +2607,16 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { operands: &[String], results: &mut Vec, ) { - results.push(format!( - "*(({}*) ({} + {}))", - ty, - operands[0], - offset.format(POINTER_SIZE_EXPRESSION) - )); + if self.gen.gen.opts.host { + results.push(format!("*(({}*) wasm_runtime_addr_app_to_native(wasm_runtime_get_module_inst(exec_env), ({} + {})))", ty, operands[0], offset.format(POINTER_SIZE_EXPRESSION))); + } else { + results.push(format!( + "*(({}*) ({} + {}))", + ty, + operands[0], + offset.format(POINTER_SIZE_EXPRESSION) + )); + } } fn load_ext( @@ -2043,14 +2632,25 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { } fn store(&mut self, ty: &str, offset: ArchitectureSize, operands: &[String]) { - uwriteln!( - self.src, - "*(({}*)({} + {})) = {};", - ty, - operands[1], - offset.format(POINTER_SIZE_EXPRESSION), - operands[0] - ); + if self.gen.gen.opts.host { + uwriteln!( + self.src, + "*(({}*)wasm_runtime_addr_app_to_native(wasm_runtime_get_module_inst(exec_env), ({} + {}))) = {};", + ty, + operands[1], + offset.format(POINTER_SIZE_EXPRESSION), + operands[0] + ); + } else { + uwriteln!( + self.src, + "*(({}*)({} + {})) = {};", + ty, + operands[1], + offset.format(POINTER_SIZE_EXPRESSION), + operands[0] + ); + } } fn has_resources2(&self, ty: &Type) -> bool { @@ -2094,6 +2694,110 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { TypeDefKind::Unknown => todo!(), } } + + fn lower_lift(&mut self, payload: Option<&Type>) -> String { + let typestr = self + .gen + .optional_type_name(payload, &self.namespace, Flavor::InStruct); + let tmpnr = self.r#gen.r#gen.tmp(); + uwriteln!(self.r#gen.r#gen.c_src_head, "struct Lift{tmpnr} {{"); + let mut bindgen = FunctionBindgen::new(self.gen, Vec::new()); + let lift = if let Some(ty) = payload { + let res = wit_bindgen_core::abi::lift_from_memory( + bindgen.r#gen.resolve, + &mut bindgen, + "ptr".into(), + ty, + ); + format!("{} return {res};", String::from(bindgen.src)) + } else { + String::new() + }; + let mut bindgen = FunctionBindgen::new(self.gen, Vec::new()); + // GuestImport doesn't leak the objects (string) + bindgen.variant = AbiVariant::GuestExport; + let lower = if let Some(ty) = payload { + wit_bindgen_core::abi::lower_to_memory( + bindgen.r#gen.resolve, + &mut bindgen, + "ptr".into(), + "value".into(), + ty, + ); + String::from(bindgen.src) + } else { + String::new() + }; + let lowered_size = if let Some(ty) = payload { + self.r#gen + .sizes + .size(ty) + .format_term(POINTER_SIZE_EXPRESSION, true) + } else { + String::from("1") + }; + + uwriteln!( + self.r#gen.r#gen.c_src_head, + "static {typestr} lift(uint8_t const* ptr) {{ {lift} }} + static void lower({typestr} && value, uint8_t *ptr) {{ {lower} }} + static constexpr size_t SIZE = {lowered_size};" + ); + uwriteln!(self.r#gen.r#gen.c_src_head, "}};"); + format!("Lift{tmpnr}") + } + + fn lower_lift_stream(&mut self, payload: Option<&Type>) { + let typestr = self + .gen + .optional_type_name(payload, &self.namespace, Flavor::InStruct); + // let tmpnr = self.r#gen.r#gen.tmp(); + let mut bindgen = FunctionBindgen::new(self.gen, Vec::new()); + let lift = if let Some(ty) = payload { + let res = wit_bindgen_core::abi::lift_from_memory( + bindgen.r#gen.resolve, + &mut bindgen, + "ptr".into(), + ty, + ); + format!("{} return {res};", String::from(bindgen.src)) + } else { + String::new() + }; + let mut bindgen = FunctionBindgen::new(self.gen, Vec::new()); + // GuestImport doesn't leak the objects (string) + bindgen.variant = AbiVariant::GuestExport; + let lower = if let Some(ty) = payload { + wit_bindgen_core::abi::lower_to_memory( + bindgen.r#gen.resolve, + &mut bindgen, + "ptr".into(), + "value".into(), + ty, + ); + String::from(bindgen.src) + } else { + String::new() + }; + let lowered_size = if let Some(ty) = payload { + self.r#gen + .sizes + .size(ty) + .format_term(POINTER_SIZE_EXPRESSION, true) + } else { + String::from("1") + }; + + uwriteln!(self.r#gen.r#gen.c_src_head, "template <> + const uint32_t wit::StreamProperties<{typestr}>::lowered_size = {lowered_size}; + template <> + {typestr} wit::StreamProperties<{typestr}>::lift(uint8_t const*ptr) {{ {lift} }} + template <> + void wit::StreamProperties<{typestr}>::lower({typestr} && value, uint8_t *ptr) {{ {lower} }}" + ); + // uwriteln!(self.r#gen.r#gen.c_src_head, "}};"); + // format!("Lift{tmpnr}") + } } fn move_if_necessary(arg: &str) -> String { @@ -2122,7 +2826,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { match inst { abi::Instruction::GetArg { nth } => { if *nth == 0 && self.params[0].as_str() == "self" { - if self.gen.in_guest_import { + if self.gen.in_guest_import ^ self.gen.gen.opts.host { results.push("(*this)".to_string()); } else { results.push("(*lookup_resource(self))".to_string()); @@ -2212,18 +2916,29 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let val = format!("vec{}", tmp); let ptr = format!("ptr{}", tmp); let len = format!("len{}", tmp); + // let result = format!("result{}", tmp); self.push_str(&format!("auto const&{} = {};\n", val, operands[0])); - self.push_str(&format!( - "auto {} = ({})({}.data());\n", - ptr, - self.gen.gen.opts.ptr_type(), - val - )); - self.push_str(&format!("auto {} = (size_t)({}.size());\n", len, val)); + if self.gen.gen.opts.host_side() { + self.push_str(&format!("auto {} = {}.data();\n", ptr, val)); + self.push_str(&format!("auto {} = {}.size();\n", len, val)); + } else { + self.push_str(&format!( + "auto {} = ({})({}.data());\n", + ptr, + self.gen.gen.opts.ptr_type(), + val + )); + self.push_str(&format!("auto {} = (size_t)({}.size());\n", len, val)); + } if realloc.is_none() { results.push(ptr); } else { - uwriteln!(self.src, "{}.leak();\n", operands[0]); + if !self.gen.gen.opts.host_side() + && !(self.gen.gen.opts.symmetric + && matches!(self.variant, AbiVariant::GuestImport)) + { + uwriteln!(self.src, "{}.leak();\n", operands[0]); + } results.push(ptr); } results.push(len); @@ -2233,18 +2948,29 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let val = format!("vec{}", tmp); let ptr = format!("ptr{}", tmp); let len = format!("len{}", tmp); + // let result = format!("result{}", tmp); self.push_str(&format!("auto const&{} = {};\n", val, operands[0])); - self.push_str(&format!( - "auto {} = ({})({}.data());\n", - ptr, - self.gen.gen.opts.ptr_type(), - val - )); - self.push_str(&format!("auto {} = (size_t)({}.size());\n", len, val)); + if self.gen.gen.opts.host_side() { + self.push_str(&format!("auto {} = {}.data();\n", ptr, val)); + self.push_str(&format!("auto {} = {}.size();\n", len, val)); + } else { + self.push_str(&format!( + "auto {} = ({})({}.data());\n", + ptr, + self.gen.gen.opts.ptr_type(), + val + )); + self.push_str(&format!("auto {} = (size_t)({}.size());\n", len, val)); + } if realloc.is_none() { results.push(ptr); } else { - uwriteln!(self.src, "{}.leak();\n", operands[0]); + if !self.gen.gen.opts.host_side() + && !(self.gen.gen.opts.symmetric + && matches!(self.variant, AbiVariant::GuestImport)) + { + uwriteln!(self.src, "{}.leak();\n", operands[0]); + } results.push(ptr); } results.push(len); @@ -2258,17 +2984,27 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let ptr = format!("ptr{}", tmp); let len = format!("len{}", tmp); self.push_str(&format!("auto const&{} = {};\n", val, operands[0])); - self.push_str(&format!( - "auto {} = ({})({}.data());\n", - ptr, - self.gen.gen.opts.ptr_type(), - val - )); - self.push_str(&format!("auto {} = (size_t)({}.size());\n", len, val)); + if self.gen.gen.opts.host_side() { + self.push_str(&format!("auto {} = {}.data();\n", ptr, val)); + self.push_str(&format!("auto {} = {}.size();\n", len, val)); + } else { + self.push_str(&format!( + "auto {} = ({})({}.data());\n", + ptr, + self.gen.gen.opts.ptr_type(), + val + )); + self.push_str(&format!("auto {} = (size_t)({}.size());\n", len, val)); + } if realloc.is_none() { results.push(ptr); } else { - uwriteln!(self.src, "{}.leak();\n", operands[0]); + if !self.gen.gen.opts.host_side() + && !(self.gen.gen.opts.symmetric + && matches!(self.variant, AbiVariant::GuestImport)) + { + uwriteln!(self.src, "{}.leak();\n", operands[0]); + } results.push(ptr); } results.push(len); @@ -2280,13 +3016,23 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { .gen .type_name(element, &self.namespace, Flavor::InStruct); self.push_str(&format!("auto {} = {};\n", len, operands[1])); - let result = if self.gen.gen.opts.new_api + let result = if self.gen.gen.opts.host { + uwriteln!(self.src, "{inner} const* ptr{tmp} = ({inner} const*)wasm_runtime_addr_app_to_native(wasm_runtime_get_module_inst(exec_env), {});\n", operands[0]); + format!("wit::span<{inner} const>(ptr{}, (size_t){len})", tmp) + } else if self.gen.gen.opts.new_api && matches!(self.variant, AbiVariant::GuestExport) { - format!( - "wit::vector<{inner} const>(({inner}*)({}), {len}).get_view()", - operands[0] - ) + if self.gen.gen.opts.symmetric { + format!( + "wit::span<{inner} const>(({inner}*)({}), {len})", + operands[0] + ) + } else { + format!( + "wit::vector<{inner} const>(({inner}*)({}), {len}).get_view()", + operands[0] + ) + } } else { format!("wit::vector<{inner}>(({inner}*)({}), {len})", operands[0]) }; @@ -2296,15 +3042,30 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let tmp = self.tmp(); let len = format!("len{}", tmp); uwriteln!(self.src, "auto {} = {};\n", len, operands[1]); - let result = if self.gen.gen.opts.new_api + let result = if self.gen.gen.opts.symmetric + && !self.gen.gen.opts.new_api && matches!(self.variant, AbiVariant::GuestExport) { - assert!(self.needs_dealloc); - uwriteln!( - self.src, - "if ({len}>0) _deallocate.push_back({});\n", - operands[0] - ); + uwriteln!(self.src, "auto string{tmp} = wit::string::from_view(std::string_view((char const *)({}), {len}));\n", operands[0]); + format!("std::move(string{tmp})") + } else if self.gen.gen.opts.host { + uwriteln!(self.src, "char const* ptr{} = (char const*)wasm_runtime_addr_app_to_native(wasm_runtime_get_module_inst(exec_env), {});\n", tmp, operands[0]); + format!("std::string_view(ptr{}, {len})", tmp) + } else if self.gen.gen.opts.short_cut + || (self.gen.gen.opts.new_api + && matches!(self.variant, AbiVariant::GuestExport)) + { + if self.gen.gen.opts.new_api + && matches!(self.variant, AbiVariant::GuestExport) + && !self.gen.gen.opts.symmetric + { + assert!(self.needs_dealloc); + uwriteln!( + self.src, + "if ({len}>0) _deallocate.push_back({});\n", + operands[0] + ); + } format!("std::string_view((char const*)({}), {len})", operands[0]) } else { format!("wit::string((char const*)({}), {len})", operands[0]) @@ -2339,7 +3100,10 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { r#"auto {result} = wit::vector<{vtype}>::allocate({len}); "#, )); - if self.gen.gen.opts.new_api && matches!(self.variant, AbiVariant::GuestExport) { + if self.gen.gen.opts.new_api + && matches!(self.variant, AbiVariant::GuestExport) + && !self.gen.gen.opts.symmetric + { assert!(self.needs_dealloc); self.push_str(&format!("if ({len}>0) _deallocate.push_back({base});\n")); } @@ -2359,9 +3123,18 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { // inplace construct uwriteln!(self.src, "{result}.initialize(i, std::move(e{tmp}));"); uwriteln!(self.src, "}}"); + if self.gen.gen.opts.new_api + && matches!(self.variant, AbiVariant::GuestImport) + && self.gen.gen.opts.symmetric + { + // we converted the result, free the returned vector + uwriteln!(self.src, "free({base});"); + } if self.gen.gen.opts.new_api && matches!(self.variant, AbiVariant::GuestExport) { results.push(format!("{result}.get_const_view()")); - if self.gen.gen.opts.new_api && matches!(self.variant, AbiVariant::GuestExport) + if !self.gen.gen.opts.symmetric + || (self.gen.gen.opts.new_api + && matches!(self.variant, AbiVariant::GuestExport)) { self.leak_on_insertion.replace(format!( "if ({len}>0) _deallocate.push_back((void*){result}.leak());\n" @@ -2395,10 +3168,21 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { .. } => { let op = &operands[0]; - if matches!(self.variant, AbiVariant::GuestImport) { - results.push(format!("{op}.into_handle()")); + if self.gen.gen.opts.host_side() { + if matches!(self.variant, AbiVariant::GuestImport) { + results.push(format!("{op}.release()->get_handle()")); + } else { + let tmp = self.tmp(); + let var = self.tempname("rep", tmp); + uwriteln!(self.src, "auto {var} = {op}.take_rep();"); + results.push(format!("{op}.get_handle()")); + } } else { - results.push(format!("{op}.release()->handle")); + if matches!(self.variant, AbiVariant::GuestImport) { + results.push(format!("{op}.into_handle()")); + } else { + results.push(format!("{op}.release()->handle")); + } } } abi::Instruction::HandleLower { @@ -2406,7 +3190,13 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { .. } => { let op = &operands[0]; - if op == "(*this)" { + if self.gen.gen.opts.host_side() { + if op == "(*this)" { + results.push(format!("{op}.get_rep()")); + } else { + results.push(format!("{op}.get().get_rep()")); + } + } else if op == "(*this)" { // TODO is there a better way to decide? results.push(format!("{op}.get_handle()")); } else { @@ -2415,7 +3205,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { } abi::Instruction::HandleLift { handle, .. } => { let op = &operands[0]; - match (handle, false) { + match (handle, self.gen.gen.opts.host_side()) { (Handle::Own(ty), true) => match self.variant { AbiVariant::GuestExport => { results.push(format!("wit::{RESOURCE_EXPORT_BASE_CLASS_NAME}{{{op}}}")) @@ -2455,7 +3245,9 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.src, "auto {var} = {tname}::Owned({tname}::ResourceRep({op}));" ); - + // if !self.gen.gen.opts.symmetric { + // uwriteln!(self.src, "{var}->into_handle();"); + // } results.push(format!("std::move({var})")) } AbiVariant::GuestImportAsync => todo!(), @@ -2569,7 +3361,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { for ty in result_types.iter() { let name = format!("variant{}", self.tmp()); results.push(name.clone()); - self.src.push_str(wit_bindgen_c::wasm_type(*ty)); + self.src.push_str(self.gen.gen.opts.wasm_type(*ty)); self.src.push_str(" "); self.src.push_str(&name); self.src.push_str(";\n"); @@ -2661,7 +3453,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let tmp = self.tmp(); let name = self.tempname("option", tmp); results.push(name.clone()); - self.src.push_str(wit_bindgen_c::wasm_type(*ty)); + self.src.push_str(self.gen.gen.opts.wasm_type(*ty)); self.src.push_str(" "); self.src.push_str(&name); self.src.push_str(";\n"); @@ -2735,7 +3527,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { let tmp = self.tmp(); let name = self.tempname("result", tmp); results.push(name.clone()); - self.src.push_str(wit_bindgen_c::wasm_type(*ty)); + self.src.push_str(self.gen.gen.opts.wasm_type(*ty)); self.src.push_str(" "); self.src.push_str(&name); self.src.push_str(";\n"); @@ -2827,7 +3619,11 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { ); results.push(resultname); } - abi::Instruction::CallWasm { name, sig } => { + abi::Instruction::CallWasm { + name, + sig, + module_prefix, + } => { let module_name = self .gen .wasm_import_module @@ -2839,38 +3635,138 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { .as_ref() .cloned() .unwrap_or_default() + + *module_prefix + e }) .unwrap(); + if self.gen.gen.opts.host { + uwriteln!(self.src, "wasm_function_inst_t wasm_func = wasm_runtime_lookup_function(wasm_runtime_get_module_inst(exec_env), \n\ + \"{}#{}\", \"{}\");", module_name, name, self.wamr_signature.as_ref().unwrap().to_string()); + if !sig.results.is_empty() { + uwriteln!( + self.src, + "wasm_val_t wasm_results[{}] = {{ WASM_INIT_VAL }};", + sig.results.len() + ); + } else { + uwriteln!(self.src, "wasm_val_t *wasm_results = nullptr;"); + } + if !sig.params.is_empty() { + uwrite!(self.src, "wasm_val_t wasm_args[{}] = {{", sig.params.len()); + for (typ, value) in sig.params.iter().zip(operands.iter()) { + match typ { + WasmType::I32 => uwrite!(self.src, "WASM_I32_VAL({}),", value), + WasmType::I64 => uwrite!(self.src, "WASM_I64_VAL({}),", value), + WasmType::F32 => uwrite!(self.src, "WASM_F32_VAL({}),", value), + WasmType::F64 => uwrite!(self.src, "WASM_F64_VAL({}),", value), + WasmType::Length => { + if self.gen.gen.opts.wasm64 { + uwrite!(self.src, "WASM_I64_VAL({}),", value) + } else { + uwrite!(self.src, "WASM_I32_VAL((int32_t){}),", value) + } + } + WasmType::Pointer => { + if self.gen.gen.opts.wasm64 { + uwrite!(self.src, "WASM_I64_VAL({}),", value) + } else { + uwrite!(self.src, "WASM_I32_VAL((int32_t){}),", value) + } + } + WasmType::PointerOrI64 => { + uwrite!(self.src, "WASM_I64_VAL({}),", value) + } + } + } + self.src.push_str("};\n"); + } else { + uwriteln!(self.src, "wasm_val_t *wasm_args = nullptr;"); + } + uwriteln!(self.src, "bool wasm_ok = wasm_runtime_call_wasm_a(exec_env, wasm_func, {}, wasm_results, {}, wasm_args);", sig.results.len(), sig.params.len()); + uwriteln!(self.src, "assert(wasm_ok);"); + if sig.results.len() > 0 { + let (kind, elem) = match sig.results.first() { + Some(WasmType::I32) => (String::from("WASM_I32"), String::from("i32")), + Some(WasmType::I64) => (String::from("WASM_I64"), String::from("i64")), + Some(WasmType::F32) => (String::from("WASM_F32"), String::from("f32")), + Some(WasmType::F64) => (String::from("WASM_F64"), String::from("f64")), + Some(WasmType::Pointer) => { + if self.gen.gen.opts.wasm64 { + (String::from("WASM_I64"), String::from("i64")) + } else { + (String::from("WASM_I32"), String::from("i32")) + } + } + Some(WasmType::Length) => { + if self.gen.gen.opts.wasm64 { + (String::from("WASM_I64"), String::from("i64")) + } else { + (String::from("WASM_I32"), String::from("i32")) + } + } + Some(WasmType::PointerOrI64) => { + (String::from("WASM_I64"), String::from("i64")) + } + None => todo!(), + }; + uwriteln!(self.src, "assert(wasm_results[0].kind=={kind});"); + uwriteln!(self.src, "auto ret = wasm_results[0].of.{elem};"); + results.push("ret".to_string()); + } + } else { + let func = + self.gen + .declare_import(&module_name, name, &sig.params, &sig.results); - let func = self - .gen - .declare_import(&module_name, name, &sig.params, &sig.results); - - // ... then call the function with all our operands - if !sig.results.is_empty() { - self.src.push_str("auto ret = "); - results.push("ret".to_string()); + // ... then call the function with all our operands + if sig.results.len() > 0 { + self.src.push_str("auto ret = "); + results.push("ret".to_string()); + } + self.src.push_str(&func); + self.src.push_str("("); + self.src.push_str(&operands.join(", ")); + self.src.push_str(");\n"); } - self.src.push_str(&func); - self.src.push_str("("); - self.src.push_str(&operands.join(", ")); - self.src.push_str(");\n"); } abi::Instruction::CallInterface { func, .. } => { // dbg!(func); self.let_results(if func.result.is_some() { 1 } else { 0 }, results); - let (namespace, func_name_h) = self.gen.func_namespace_name(func, true, true); + let (mut namespace, func_name_h) = + self.gen + .func_namespace_name(func, !self.gen.gen.opts.host_side(), true); if matches!(func.kind, FunctionKind::Method(_)) { let this = operands.remove(0); - uwrite!(self.src, "({this}).get()."); + if self.gen.gen.opts.host_side() { + uwrite!(self.src, "({this})."); + } else { + uwrite!(self.src, "({this}).get()."); + } } else { + if matches!(func.kind, FunctionKind::Constructor(_)) + && self.gen.gen.opts.host_side() + { + let _ = namespace.pop(); + } let mut relative = SourceWithState::default(); relative.qualify(&namespace); self.push_str(&relative.src); } self.src.push_str(&func_name_h); + if matches!(func.kind, FunctionKind::Constructor(_)) + && self.gen.gen.opts.host_side() + { + self.push_str("::New"); + } self.push_str("("); + if self.gen.gen.opts.host { + if !matches!(func.kind, FunctionKind::Method(_)) { + self.push_str("exec_env"); + if !operands.is_empty() { + self.push_str(", "); + } + } + } self.push_str(&operands.join(", ")); self.push_str(");\n"); if self.needs_dealloc { @@ -2925,20 +3821,35 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { ret_type: _cabi_post_type, }) = self.cabi_post.as_ref() { - let cabi_post_name = self.gen.declare_import( - &format!("cabi_post_{func_module}"), - func_name, - &[WasmType::Pointer], - &[], - ); - self.src.push_str(&format!(", ret, {})", cabi_post_name)); + if self.gen.gen.opts.host { + let cabi_post_name = make_external_symbol( + &func_module, + &func_name, + AbiVariant::GuestExport, + ); + self.src.push_str(&format!(", wasm_results[0].of.i32, wasm_runtime_lookup_function(wasm_runtime_get_module_inst(exec_env), \"cabi_post_{}\", \"(i)\"), exec_env)", cabi_post_name)); + } else { + let cabi_post_name = self.gen.declare_import( + &format!("cabi_post_{func_module}"), + func_name, + &[WasmType::Pointer], + &[], + ); + self.src.push_str(&format!(", ret, {})", cabi_post_name)); + } } if matches!(func.kind, FunctionKind::Constructor(_)) && self.gen.gen.opts.is_only_handle(self.variant) { // we wrapped the handle in an object, so unpack it - - self.src.push_str(".into_handle()"); + if self.gen.gen.opts.host_side() { + self.src.push_str( + ".get_handle(); + this->rep = *lookup_resource(ret)", + ); + } else { + self.src.push_str(".into_handle()"); + } } self.src.push_str(";\n"); } @@ -3007,10 +3918,54 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> { self.store(ptr_type, *offset, operands) } abi::Instruction::LengthStore { offset } => self.store("size_t", *offset, operands), - abi::Instruction::FutureLower { .. } => todo!(), - abi::Instruction::FutureLift { .. } => todo!(), - abi::Instruction::StreamLower { .. } => todo!(), - abi::Instruction::StreamLift { .. } => todo!(), + abi::Instruction::FutureLower { payload, .. } => { + let lower_lift = self.lower_lift(payload.as_ref()); + results.push(format!( + "lower_future<{}, {lower_lift}>(std::move({}))", + self.gen.optional_type_name( + payload.as_ref(), + &self.namespace, + Flavor::InStruct + ), + operands[0] + )); + } + abi::Instruction::FutureLift { payload, .. } => { + let lower_lift = self.lower_lift(payload.as_ref()); + results.push(format!( + "lift_future<{}, {lower_lift}>({})", + self.gen.optional_type_name( + payload.as_ref(), + &self.namespace, + Flavor::InStruct + ), + operands[0] + )); + } + abi::Instruction::StreamLower { payload, .. } => { + self.lower_lift_stream(payload.as_ref()); + results.push(format!( + "lower_stream<{}>(std::move({}))", + self.gen.optional_type_name( + payload.as_ref(), + &self.namespace, + Flavor::InStruct + ), + operands[0] + )); + } + abi::Instruction::StreamLift { payload, .. } => { + self.lower_lift_stream(payload.as_ref()); + results.push(format!( + "lift_stream<{}>({})", + self.gen.optional_type_name( + payload.as_ref(), + &self.namespace, + Flavor::InStruct + ), + operands[0] + )); + } abi::Instruction::ErrorContextLower { .. } => todo!(), abi::Instruction::ErrorContextLift { .. } => todo!(), abi::Instruction::Flush { amt } => { diff --git a/crates/cpp/src/wamr.rs b/crates/cpp/src/wamr.rs new file mode 100644 index 000000000..fab0c6f71 --- /dev/null +++ b/crates/cpp/src/wamr.rs @@ -0,0 +1,144 @@ +use wit_bindgen_core::wit_parser::{Function, Resolve, Type, TypeDefKind}; + +#[derive(Debug, Default)] +pub struct WamrSig { + wamr_types: String, + wamr_result: String, +} + +impl ToString for WamrSig { + fn to_string(&self) -> String { + "(".to_string() + &self.wamr_types + ")" + &self.wamr_result + } +} + +fn push_wamr(ty: &Type, resolve: &Resolve, params_str: &mut String) { + match ty { + Type::Bool + | Type::U8 + | Type::U16 + | Type::U32 + | Type::S8 + | Type::S16 + | Type::S32 + | Type::Char => { + params_str.push('i'); + } + Type::U64 | Type::S64 => { + params_str.push('I'); + } + Type::F32 => { + params_str.push('f'); + } + Type::F64 => { + params_str.push('F'); + } + Type::String => { + params_str.push_str("$~"); + } + Type::Id(id) => match &resolve.types[*id].kind { + TypeDefKind::Type(t) => push_wamr(t, resolve, params_str), + TypeDefKind::Record(_r) => { + params_str.push_str("R"); + } + TypeDefKind::Flags(_) => params_str.push_str("L"), + TypeDefKind::Tuple(_) => params_str.push_str("T"), + TypeDefKind::Variant(_) => params_str.push_str("V"), + TypeDefKind::Enum(_e) => { + params_str.push_str("i"); + } + TypeDefKind::Option(_) => params_str.push_str("O"), + TypeDefKind::Result(_) => params_str.push_str("R"), + TypeDefKind::List(_t) => { + params_str.push_str("*~"); + } + TypeDefKind::Future(_) => todo!(), + TypeDefKind::Stream(_) => todo!(), + TypeDefKind::Unknown => todo!(), + TypeDefKind::Resource => { + params_str.push('i'); + } + TypeDefKind::Handle(_h) => { + params_str.push('i'); + } + TypeDefKind::FixedSizeList(_, _) => todo!(), + }, + Type::ErrorContext => todo!(), + } +} + +fn wamr_add_result(sig: &mut WamrSig, resolve: &Resolve, ty: &Type) { + match ty { + Type::Bool + | Type::U8 + | Type::U16 + | Type::U32 + | Type::S8 + | Type::S16 + | Type::S32 + | Type::Char => { + sig.wamr_result = "i".into(); + } + Type::S64 | Type::U64 => { + sig.wamr_result = "I".into(); + } + Type::F32 => { + sig.wamr_result = "f".into(); + } + Type::F64 => { + sig.wamr_result = "F".into(); + } + Type::String => { + sig.wamr_types.push('*'); + } + Type::Id(id) => match &resolve.types[*id].kind { + TypeDefKind::Record(_r) => sig.wamr_types.push('R'), + TypeDefKind::Flags(fl) => { + sig.wamr_types + .push(if fl.flags.len() > 32 { 'I' } else { 'i' }) + } + TypeDefKind::Tuple(_) => sig.wamr_result.push('i'), + TypeDefKind::Variant(_) => { + sig.wamr_types.push('*'); + } + TypeDefKind::Enum(_e) => { + sig.wamr_types.push('*'); + } + TypeDefKind::Option(_o) => { + sig.wamr_types.push('*'); + } + TypeDefKind::Result(_) => { + sig.wamr_types.push('*'); + } + TypeDefKind::List(_) => { + sig.wamr_types.push('*'); + } + TypeDefKind::Future(_) => sig.wamr_types.push('*'), + TypeDefKind::Stream(_) => sig.wamr_types.push('*'), + TypeDefKind::Type(ty) => wamr_add_result(sig, resolve, &ty), + TypeDefKind::Unknown => todo!(), + TypeDefKind::Resource => { + // resource-rep is returning a pointer + // perhaps i??? + sig.wamr_result = "*".into(); + } + TypeDefKind::Handle(_h) => { + sig.wamr_result = "i".into(); + } + TypeDefKind::FixedSizeList(_, _) => todo!(), + }, + Type::ErrorContext => todo!(), + } +} + +pub fn wamr_signature(resolve: &Resolve, func: &Function) -> WamrSig { + let mut result = WamrSig::default(); + for (_name, param) in func.params.iter() { + push_wamr(param, resolve, &mut result.wamr_types); + } + match &func.result { + Some(ty) => wamr_add_result(&mut result, resolve, ty), + None => {} + } + result +} diff --git a/crates/cpp/test_headers/wasm_c_api.h b/crates/cpp/test_headers/wasm_c_api.h new file mode 100644 index 000000000..6cb1a09bf --- /dev/null +++ b/crates/cpp/test_headers/wasm_c_api.h @@ -0,0 +1,26 @@ +#pragma once +#include + +typedef uint8_t wasm_valkind_t; +enum wasm_valkind_enum { + WASM_I32, + WASM_I64, + WASM_F32, + WASM_F64, +}; +typedef struct wasm_val_t { + wasm_valkind_t kind; + uint8_t __padding[7]; + union { + int32_t i32; + int64_t i64; + float f32; + double f64; + } of; +} wasm_val_t; + +#define WASM_INIT_VAL {.kind = WASM_I32, .of = {.i32 = 0}} +#define WASM_I32_VAL(x) {.kind = WASM_I32, .of = {.i32 =(x)}} +#define WASM_I64_VAL(x) {.kind = WASM_I64, .of = {.i64 =(x)}} +#define WASM_F32_VAL(x) {.kind = WASM_F32, .of = {.f32 =(x)}} +#define WASM_F64_VAL(x) {.kind = WASM_F64, .of = {.f64 =(x)}} diff --git a/crates/cpp/test_headers/wasm_export.h b/crates/cpp/test_headers/wasm_export.h new file mode 100644 index 000000000..0b4b48aff --- /dev/null +++ b/crates/cpp/test_headers/wasm_export.h @@ -0,0 +1,19 @@ +#pragma once +// minimal WAMR header mock-up for compilation tests +#include +struct WASMExecEnv; +typedef WASMExecEnv* wasm_exec_env_t; +struct WASMModuleInstanceCommon; +typedef WASMModuleInstanceCommon* wasm_module_inst_t; +typedef void* wasm_function_inst_t; +wasm_module_inst_t wasm_runtime_get_module_inst(wasm_exec_env_t); +void* wasm_runtime_addr_app_to_native(wasm_module_inst_t,int32_t); +struct NativeSymbol { + const char* name; + void* func; + const char* signature; + void* env; +}; +void wasm_runtime_register_natives(char const* module, NativeSymbol const*, unsigned); +bool wasm_runtime_call_wasm_a(wasm_exec_env_t, wasm_function_inst_t, uint32_t, struct wasm_val_t*, uint32_t, struct wasm_val_t*); +wasm_function_inst_t wasm_runtime_lookup_function(wasm_module_inst_t, const char*, const char*); diff --git a/crates/cpp/test_headers/wit-host.h b/crates/cpp/test_headers/wit-host.h new file mode 120000 index 000000000..89d7c0a62 --- /dev/null +++ b/crates/cpp/test_headers/wit-host.h @@ -0,0 +1 @@ +../helper-types/wit-host.h \ No newline at end of file diff --git a/crates/cpp/tests/.gitignore b/crates/cpp/tests/.gitignore new file mode 100644 index 000000000..83a8c48a0 --- /dev/null +++ b/crates/cpp/tests/.gitignore @@ -0,0 +1,3 @@ +*.o +*.template +.vscode diff --git a/crates/cpp/tests/README.md b/crates/cpp/tests/README.md new file mode 100644 index 000000000..27ef59264 --- /dev/null +++ b/crates/cpp/tests/README.md @@ -0,0 +1,23 @@ + +This folder contains examples on how to use the canonical ABI without +a wasm32 target. + +The `native_strings` folder contains an example of passing strings, with +the guest in C++ and Rust, the host in C++, and in the w2c folder an +example of a wasm component transpiled to C and then executed natively. +The wamr folder creates a fully binary compatible shared object linking to +wasm-micro-runtime and interpreting the wasm binary. + +Please note that this demonstrates that native compilation, wasm2c and wamr are +binary compatible and fully exchangeable. + +Sadly the [w2c2](https://github.com/turbolent/w2c2) bridge code generation isn't yet complete. + +The `native_resources` folder shows a more complex example using resources, +both guest and host defined ones. This doesn't include a wasm2c deployment. + +The `native_mesh` folder shows an example with resources and more than one +component. Optimizing this is work in progress. + +The `meshless_resources` and `meshless_strings` folders experiment +with directly linking two components in a shared everything environment. diff --git a/crates/cpp/tests/codegen.rs b/crates/cpp/tests/codegen.rs new file mode 100644 index 000000000..948d109e1 --- /dev/null +++ b/crates/cpp/tests/codegen.rs @@ -0,0 +1,160 @@ +use heck::*; +use std::env; +use std::path::{Path, PathBuf}; +use std::process::Command; + +macro_rules! codegen_test { + ($id:ident $name:tt $test:tt) => { + #[test] + fn $id() { + if [ + "go_params", + "guest-name", + "import-func", + "import-and-export-resource", + "import-and-export-resource-alias", + "interface-has-go-keyword", + "issue551", + "issue573", + "issue607", + "issue668", + "issue929", + "issue929-no-export", + "issue929-no-import", + "issue929-only-methods", + "small-anonymous", + "wasi-cli", + "wasi-clocks", + "wasi-filesystem", + "wasi-http", + "wasi-io", + "keywords", + "lift-lower-foreign", + "lists", + "multi-return", + "multiversion", + "option-result", + "record-has-go-keyword-and-used-in-fn", + "resource-alias", + "resource-borrow-in-record", + "resource-borrow-in-record-export", + "resource-local-alias", + "resource-local-alias-borrow", + "resource-local-alias-borrow-import", + "resource-own-in-other-interface", + "resources", + "resources-in-aggregates", + "resources-with-lists", + "result-empty", + "ret-areas", + "return-resource-from-export", + "same-names5", + "simple-http", + // "simple-lists", + "use-across-interfaces", + "variants", + "variants-unioning-types", + "worlds-with-types", + "zero-size-tuple", + ] + .contains(&$name) + { + let test_all_code = env::var_os("CPP_ALL_TESTS").is_some(); + if !test_all_code { + return; + } + } + test_helpers::run_world_codegen_test( + "cpp", + $test.as_ref(), + |resolve, world, files| { + let mut opts = wit_bindgen_cpp::Opts::default(); + opts.new_api = true; + opts.build().generate(resolve, world, files).unwrap() + }, + verify, + ); + let test_host_code = env::var_os("CPP_HOST_TESTS").is_some(); + if test_host_code { + test_helpers::run_world_codegen_test( + "cpp-host", + $test.as_ref(), + |resolve, world, files| { + let mut opts = wit_bindgen_cpp::Opts::default(); + opts.host = true; + opts.build().generate(resolve, world, files).unwrap() + }, + verify_host, + ); + } + } + }; +} + +test_helpers::codegen_tests!(); + +fn verify(dir: &Path, name: &str) { + let name = name.to_snake_case(); + let sdk_path = PathBuf::from( + env::var_os("WASI_SDK_PATH").expect("environment variable WASI_SDK_PATH should be set"), + ); + let sysroot = sdk_path.join("share/wasi-sysroot"); + let c_src = dir.join(format!("{name}.cpp")); + let additional_includes = PathBuf::from( + env::var_os("CARGO_MANIFEST_DIR") + .expect("environment variable CARGO_MANIFEST_DIR should get set by cargo"), + ) + .join("test_headers"); + + let shared_args = vec![ + "--sysroot", + sysroot.to_str().unwrap(), + "-I", + dir.to_str().unwrap(), + "-I", + additional_includes.to_str().unwrap(), + // "-Wall", + // "-Wextra", + // "-Werror", + // "-Wno-unused-parameter", + "-std=c++2b", + "-c", + "-o", + ]; + + let mut cmd = Command::new(sdk_path.join("bin/clang++")); + cmd.args(&shared_args); + cmd.arg(dir.join("obj.o")); + cmd.arg(&c_src); + test_helpers::run_command(&mut cmd); +} + +fn verify_host(dir: &Path, name: &str) { + let name = name.to_snake_case(); + let c_src = dir.join(format!("{name}_host.cpp")); + let additional_includes = PathBuf::from( + env::var_os("CARGO_MANIFEST_DIR") + .expect("environment variable CARGO_MANIFEST_DIR should get set by cargo"), + ) + .join("test_headers"); + + let shared_args = vec![ + "-I", + dir.to_str().unwrap(), + "-I", + additional_includes.to_str().unwrap(), + // "-Wall", + // "-Wextra", + // "-Werror", + // "-Wno-unused-parameter", + "-std=c++2b", + "-c", + "-o", + ]; + + let mut cmd = Command::new("clang++"); + cmd.args(&shared_args); + cmd.arg(dir.join("obj.o")); + cmd.arg(&c_src); + test_helpers::run_command(&mut cmd); +} diff --git a/crates/cpp/tests/meshless_resources/.gitignore b/crates/cpp/tests/meshless_resources/.gitignore new file mode 100644 index 000000000..aef20e490 --- /dev/null +++ b/crates/cpp/tests/meshless_resources/.gitignore @@ -0,0 +1,2 @@ +/component_a/component_a +/component_b/libcomponent_b.a diff --git a/crates/cpp/tests/meshless_resources/Makefile b/crates/cpp/tests/meshless_resources/Makefile new file mode 100644 index 000000000..bb29df627 --- /dev/null +++ b/crates/cpp/tests/meshless_resources/Makefile @@ -0,0 +1,13 @@ + +all: + make -C component_b $@ + make -C component_a $@ + +bindgen: + make -C component_b $@ + make -C component_a $@ + +clean: + make -C component_b $@ + make -C component_a $@ + \ No newline at end of file diff --git a/crates/cpp/tests/meshless_resources/component_a/Makefile b/crates/cpp/tests/meshless_resources/component_a/Makefile new file mode 100644 index 000000000..bf15a3c65 --- /dev/null +++ b/crates/cpp/tests/meshless_resources/component_a/Makefile @@ -0,0 +1,12 @@ +CXXFLAGS=-g -O0 -I../../../helper-types + +all: component_a + +component_a: a.cpp main.cpp + $(CXX) $(CXXFLAGS) -o $@ $^ -L../component_b -lcomponent_b + +bindgen: + ../../../../../target/debug/wit-bindgen cpp ../wit -w a --symmetric --wasm64 --format + +clean: + -rm *~ component_a *.o diff --git a/crates/cpp/tests/meshless_resources/component_a/a.cpp b/crates/cpp/tests/meshless_resources/component_a/a.cpp new file mode 120000 index 000000000..7975d92df --- /dev/null +++ b/crates/cpp/tests/meshless_resources/component_a/a.cpp @@ -0,0 +1 @@ +../../native_mesh/component_a/a.cpp \ No newline at end of file diff --git a/crates/cpp/tests/meshless_resources/component_a/a_cpp.h b/crates/cpp/tests/meshless_resources/component_a/a_cpp.h new file mode 120000 index 000000000..6a689e8e5 --- /dev/null +++ b/crates/cpp/tests/meshless_resources/component_a/a_cpp.h @@ -0,0 +1 @@ +../../native_mesh/component_a/a_cpp.h \ No newline at end of file diff --git a/crates/cpp/tests/meshless_resources/component_a/main.cpp b/crates/cpp/tests/meshless_resources/component_a/main.cpp new file mode 120000 index 000000000..c5bb33e5d --- /dev/null +++ b/crates/cpp/tests/meshless_resources/component_a/main.cpp @@ -0,0 +1 @@ +../../native_mesh/component_a/main.cpp \ No newline at end of file diff --git a/crates/cpp/tests/meshless_resources/component_b/Makefile b/crates/cpp/tests/meshless_resources/component_b/Makefile new file mode 100644 index 000000000..c871371a6 --- /dev/null +++ b/crates/cpp/tests/meshless_resources/component_b/Makefile @@ -0,0 +1,12 @@ +CXXFLAGS=-g -O0 -I../../../helper-types + +all: libcomponent_b.a + +libcomponent_b.a: b.o impl.o + ar rcvs $@ $^ + +bindgen: + ../../../../../target/debug/wit-bindgen cpp ../wit -w b --symmetric --wasm64 --format + +clean: + -rm *~ *.a *.o diff --git a/crates/cpp/tests/meshless_resources/component_b/b.cpp b/crates/cpp/tests/meshless_resources/component_b/b.cpp new file mode 100644 index 000000000..501cd2ece --- /dev/null +++ b/crates/cpp/tests/meshless_resources/component_b/b.cpp @@ -0,0 +1,69 @@ +// Generated by `wit-bindgen` 0.3.0. DO NOT EDIT! + +// Ensure that the *_component_type.o object is linked in +#ifdef __wasm32__ +extern void __component_type_object_force_link_b(void); +void __component_type_object_force_link_b_public_use_in_this_compilation_unit( + void) { + __component_type_object_force_link_b(); +} +#endif +#include "b_cpp.h" +#include // realloc + +extern "C" void *cabi_realloc(void *ptr, size_t old_size, size_t align, + size_t new_size); + +__attribute__((__weak__, __export_name__("cabi_realloc"))) void * +cabi_realloc(void *ptr, size_t old_size, size_t align, size_t new_size) { + (void)old_size; + if (new_size == 0) + return (void *)align; + void *ret = realloc(ptr, new_size); + if (!ret) + abort(); + return ret; +} + +extern "C" + __attribute__((__export_name__("foo:foo/resources#[resource_drop]r"))) void + fooX3AfooX2FresourcesX00X5Bresource_dropX5Dr(uint8_t *arg0) { + exports::foo::foo::resources::R::ResourceDrop((uint8_t *)arg0); +} +extern "C" __attribute__((__export_name__("foo:foo/resources#[constructor]r"))) +uint8_t * +fooX3AfooX2FresourcesX00X5BconstructorX5Dr(int32_t arg0) { + auto result0 = exports::foo::foo::resources::R::New((uint32_t(arg0))); + return result0.release()->handle; +} +extern "C" + __attribute__((__export_name__("foo:foo/resources#[method]r.add"))) void + fooX3AfooX2FresourcesX00X5BmethodX5DrX2Eadd(uint8_t *arg0, int32_t arg1) { + (std::ref(*(exports::foo::foo::resources::R *)arg0)) + .get() + .Add((uint32_t(arg1))); +} +uint8_t *exports::foo::foo::resources::R::ResourceNew(R *self) { + return (uint8_t *)self; +} +exports::foo::foo::resources::R * +exports::foo::foo::resources::R::ResourceRep(uint8_t *id) { + return (exports::foo::foo::resources::R *)id; +} +void exports::foo::foo::resources::R::ResourceDrop(uint8_t *id) { + exports::foo::foo::resources::R::Dtor((exports::foo::foo::resources::R *)id); +} +extern "C" __attribute__((__export_name__("foo:foo/resources#create"))) +uint8_t * +fooX3AfooX2FresourcesX00create() { + auto result0 = exports::foo::foo::resources::Create(); + return result0.release()->handle; +} +extern "C" __attribute__((__export_name__("foo:foo/resources#consume"))) void +fooX3AfooX2FresourcesX00consume(uint8_t *arg0) { + auto obj0 = exports::foo::foo::resources::R::Owned( + exports::foo::foo::resources::R::ResourceRep(arg0)); + exports::foo::foo::resources::Consume(std::move(obj0)); +} + +// Component Adapters diff --git a/crates/cpp/tests/meshless_resources/component_b/b_cpp.h b/crates/cpp/tests/meshless_resources/component_b/b_cpp.h new file mode 100644 index 000000000..39cfdad62 --- /dev/null +++ b/crates/cpp/tests/meshless_resources/component_b/b_cpp.h @@ -0,0 +1,20 @@ +// Generated by `wit-bindgen` 0.3.0. DO NOT EDIT! +#ifndef __CPP_GUEST_BINDINGS_B_H +#define __CPP_GUEST_BINDINGS_B_H +#define WIT_SYMMETRIC +#include "exports-foo-foo-resources-R.h" +#include +#include +// export_interface Interface(Id { idx: 0 }) +namespace exports { +namespace foo { +namespace foo { +namespace resources { +R::Owned Create(); +void Consume(R::Owned o); +} // namespace resources +} // namespace foo +} // namespace foo +} // namespace exports + +#endif diff --git a/crates/cpp/tests/meshless_resources/component_b/exports-foo-foo-resources-R.h b/crates/cpp/tests/meshless_resources/component_b/exports-foo-foo-resources-R.h new file mode 100644 index 000000000..25796f33d --- /dev/null +++ b/crates/cpp/tests/meshless_resources/component_b/exports-foo-foo-resources-R.h @@ -0,0 +1,32 @@ +#pragma once +#define WIT_SYMMETRIC +#include +#include +#include +#include +/* User class definition file, autogenerated once, then user modified + * Updated versions of this file are generated into R.template. + */ +namespace exports { +namespace foo { +namespace foo { +namespace resources { +class R : public wit::ResourceExportBase { + uint32_t value; + +public: + static void Dtor(R *self) { delete self; } + R(uint32_t a) : value(a) {} + static Owned New(uint32_t a) { return Owned(new R(a)); } + void Add(uint32_t b) { value += b; } + static uint8_t* ResourceNew(R *self); + static R *ResourceRep(uint8_t* id); + static void ResourceDrop(uint8_t* id); + + uint32_t get_value() const { return value; } +}; + +} // namespace resources +} // namespace foo +} // namespace foo +} // namespace exports diff --git a/crates/cpp/tests/meshless_resources/component_b/impl.cpp b/crates/cpp/tests/meshless_resources/component_b/impl.cpp new file mode 120000 index 000000000..a95bdb47c --- /dev/null +++ b/crates/cpp/tests/meshless_resources/component_b/impl.cpp @@ -0,0 +1 @@ +../../native_mesh/component_b/impl.cpp \ No newline at end of file diff --git a/crates/cpp/tests/meshless_resources/rust_comp_a/.gitignore b/crates/cpp/tests/meshless_resources/rust_comp_a/.gitignore new file mode 100644 index 000000000..ea8c4bf7f --- /dev/null +++ b/crates/cpp/tests/meshless_resources/rust_comp_a/.gitignore @@ -0,0 +1 @@ +/target diff --git a/crates/cpp/tests/meshless_resources/rust_comp_a/Cargo.lock b/crates/cpp/tests/meshless_resources/rust_comp_a/Cargo.lock new file mode 100644 index 000000000..2578eb661 --- /dev/null +++ b/crates/cpp/tests/meshless_resources/rust_comp_a/Cargo.lock @@ -0,0 +1,14 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "rust_comp_a" +version = "0.1.0" +dependencies = [ + "rust_comp_b", +] + +[[package]] +name = "rust_comp_b" +version = "0.1.0" diff --git a/crates/cpp/tests/meshless_resources/rust_comp_a/Cargo.toml b/crates/cpp/tests/meshless_resources/rust_comp_a/Cargo.toml new file mode 100644 index 000000000..c36e84d15 --- /dev/null +++ b/crates/cpp/tests/meshless_resources/rust_comp_a/Cargo.toml @@ -0,0 +1,9 @@ +[workspace] + +[package] +name = "rust_comp_a" +version = "0.1.0" +edition = "2021" + +[dependencies] +rust_comp_b = { path = "../rust_comp_b" } diff --git a/crates/cpp/tests/meshless_resources/rust_comp_a/Makefile b/crates/cpp/tests/meshless_resources/rust_comp_a/Makefile new file mode 100644 index 000000000..2e7b4f365 --- /dev/null +++ b/crates/cpp/tests/meshless_resources/rust_comp_a/Makefile @@ -0,0 +1,3 @@ + +bindgen: + (cd src; ../../../../../../target/debug/wit-bindgen rust ../../wit/resources_simple.wit -w a --with foo:foo/resources=generate) diff --git a/crates/cpp/tests/meshless_resources/rust_comp_a/build.rs b/crates/cpp/tests/meshless_resources/rust_comp_a/build.rs new file mode 100644 index 000000000..6f2a100df --- /dev/null +++ b/crates/cpp/tests/meshless_resources/rust_comp_a/build.rs @@ -0,0 +1,7 @@ +use std::env; + +fn main() { + let source_dir = env::var("OUT_DIR").unwrap(); + println!("cargo:rustc-link-search=native={}/deps", source_dir); + println!("cargo:rustc-link-lib=dylib=rust_comp_b"); +} diff --git a/crates/cpp/tests/meshless_resources/rust_comp_a/src/a.rs b/crates/cpp/tests/meshless_resources/rust_comp_a/src/a.rs new file mode 100644 index 000000000..d2dd5e071 --- /dev/null +++ b/crates/cpp/tests/meshless_resources/rust_comp_a/src/a.rs @@ -0,0 +1,303 @@ +// Generated by `wit-bindgen` 0.28.0. DO NOT EDIT! +// Options used: +#[allow(dead_code)] +pub mod foo { + #[allow(dead_code)] + pub mod foo { + #[allow(dead_code, clippy::all)] + pub mod resources { + #[used] + #[doc(hidden)] + #[cfg(target_arch = "wasm32")] + static __FORCE_SECTION_REF: fn() = super::super::super::__link_custom_section_describing_imports; + use super::super::super::_rt; + + #[derive(Debug)] + #[repr(transparent)] + pub struct R{ + handle: _rt::Resource, + } + + impl R{ + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + Self { + handle: _rt::Resource::from_handle(handle), + } + } + + #[doc(hidden)] + pub fn take_handle(&self) -> usize{ + _rt::Resource::take_handle(&self.handle) + } + + #[doc(hidden)] + pub fn handle(&self) -> usize{ + _rt::Resource::handle(&self.handle) + } + } + + + unsafe impl _rt::WasmResource for R{ + #[inline] + unsafe fn drop(_handle: usize) { + { + #[link(wasm_import_module = "foo:foo/resources")] + extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "[resource-drop]r")] + fn fooX3AfooX2FresourcesX00X5Bresource_dropX5Dr(_: usize); + } + + fooX3AfooX2FresourcesX00X5Bresource_dropX5Dr(_handle); + } + } + } + + impl R { + #[allow(unused_unsafe, clippy::all)] + pub fn new(a: u32,) -> Self{ + unsafe { + + #[link(wasm_import_module = "foo:foo/resources")] + extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "[constructor]r")] + fn fooX3AfooX2FresourcesX00X5BconstructorX5Dr(_: i32, ) -> *mut u8; + } + let ret = fooX3AfooX2FresourcesX00X5BconstructorX5Dr(_rt::as_i32(&a)); + R::from_handle(ret as usize) + } + } + } + impl R { + #[allow(unused_unsafe, clippy::all)] + pub fn add(&self,b: u32,) -> (){ + unsafe { + + #[link(wasm_import_module = "foo:foo/resources")] + extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "[method]r.add")] + fn fooX3AfooX2FresourcesX00X5BmethodX5DrX2Eadd(_: *mut u8, _: i32, ); + } + fooX3AfooX2FresourcesX00X5BmethodX5DrX2Eadd((self).handle() as *mut u8, _rt::as_i32(&b)); + } + } + } + #[allow(unused_unsafe, clippy::all)] + pub fn create() -> R{ + unsafe { + + #[link(wasm_import_module = "foo:foo/resources")] + extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "create")] + fn fooX3AfooX2FresourcesX00create() -> *mut u8; + } + let ret = fooX3AfooX2FresourcesX00create(); + R::from_handle(ret as usize) + } + } + #[allow(unused_unsafe, clippy::all)] + /// borrows: func(o: borrow); + pub fn consume(o: R,) -> (){ + unsafe { + + #[link(wasm_import_module = "foo:foo/resources")] + extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "consume")] + fn fooX3AfooX2FresourcesX00consume(_: *mut u8, ); + } + fooX3AfooX2FresourcesX00consume((&o).take_handle() as *mut u8); + } + } + + } + + } +} +mod _rt { + + + use core::fmt; + use core::marker; + use core::sync::atomic::{AtomicUsize, Ordering::Relaxed}; + + /// A type which represents a component model resource, either imported or + /// exported into this component. + /// + /// This is a low-level wrapper which handles the lifetime of the resource + /// (namely this has a destructor). The `T` provided defines the component model + /// intrinsics that this wrapper uses. + /// + /// One of the chief purposes of this type is to provide `Deref` implementations + /// to access the underlying data when it is owned. + /// + /// This type is primarily used in generated code for exported and imported + /// resources. + #[repr(transparent)] + pub struct Resource { + // NB: This would ideally be `usize` but it is not. The fact that this has + // interior mutability is not exposed in the API of this type except for the + // `take_handle` method which is supposed to in theory be private. + // + // This represents, almost all the time, a valid handle value. When it's + // invalid it's stored as `0`. + handle: AtomicUsize, + _marker: marker::PhantomData, + } + + /// A trait which all wasm resources implement, namely providing the ability to + /// drop a resource. + /// + /// This generally is implemented by generated code, not user-facing code. + #[allow(clippy::missing_safety_doc)] + pub unsafe trait WasmResource { + /// Invokes the `[resource-drop]...` intrinsic. + unsafe fn drop(handle: usize); + } + + impl Resource { + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + debug_assert!(handle != 0); + Self { + handle: AtomicUsize::new(handle), + _marker: marker::PhantomData, + } + } + + /// Takes ownership of the handle owned by `resource`. + /// + /// Note that this ideally would be `into_handle` taking `Resource` by + /// ownership. The code generator does not enable that in all situations, + /// unfortunately, so this is provided instead. + /// + /// Also note that `take_handle` is in theory only ever called on values + /// owned by a generated function. For example a generated function might + /// take `Resource` as an argument but then call `take_handle` on a + /// reference to that argument. In that sense the dynamic nature of + /// `take_handle` should only be exposed internally to generated code, not + /// to user code. + #[doc(hidden)] + pub fn take_handle(resource: &Resource) -> usize { + resource.handle.swap(0, Relaxed) + } + + #[doc(hidden)] + pub fn handle(resource: &Resource) -> usize { + resource.handle.load(Relaxed) + } + } + + impl fmt::Debug for Resource { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Resource") + .field("handle", &self.handle) + .finish() + } + } + + impl Drop for Resource { + fn drop(&mut self) { + unsafe { + match self.handle.load(Relaxed) { + // If this handle was "taken" then don't do anything in the + // destructor. + 0 => {} + + // ... but otherwise do actually destroy it with the imported + // component model intrinsic as defined through `T`. + other => T::drop(other), + } + } + } + } + + pub fn as_i32(t: T) -> i32 { + t.as_i32() + } + + pub trait AsI32 { + fn as_i32(self) -> i32; + } + + impl<'a, T: Copy + AsI32> AsI32 for &'a T { + fn as_i32(self) -> i32 { + (*self).as_i32() + } + } + + impl AsI32 for i32 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + + impl AsI32 for u32 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + + impl AsI32 for i16 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + + impl AsI32 for u16 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + + impl AsI32 for i8 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + + impl AsI32 for u8 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + + impl AsI32 for char { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + + impl AsI32 for usize { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } +} + +#[cfg(target_arch = "wasm32")] +#[link_section = "component-type:wit-bindgen:0.28.0:a:encoded world"] +#[doc(hidden)] +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 272] = *b"\ +\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\x98\x01\x01A\x02\x01\ +A\x02\x01B\x0b\x04\0\x01r\x03\x01\x01i\0\x01@\x01\x01ay\0\x01\x04\0\x0e[construc\ +tor]r\x01\x02\x01h\0\x01@\x02\x04self\x03\x01by\x01\0\x04\0\x0d[method]r.add\x01\ +\x04\x01@\0\0\x01\x04\0\x06create\x01\x05\x01@\x01\x01o\x01\x01\0\x04\0\x07consu\ +me\x01\x06\x03\x01\x11foo:foo/resources\x05\0\x04\x01\x09foo:foo/a\x04\0\x0b\x07\ +\x01\0\x01a\x03\0\0\0G\x09producers\x01\x0cprocessed-by\x02\x0dwit-component\x07\ +0.214.0\x10wit-bindgen-rust\x060.28.0"; + +#[inline(never)] +#[doc(hidden)] +#[cfg(target_arch = "wasm32")] +pub fn __link_custom_section_describing_imports() { + wit_bindgen::rt::maybe_link_cabi_realloc(); +} + diff --git a/crates/cpp/tests/meshless_resources/rust_comp_a/src/main.rs b/crates/cpp/tests/meshless_resources/rust_comp_a/src/main.rs new file mode 100644 index 000000000..9acc454d7 --- /dev/null +++ b/crates/cpp/tests/meshless_resources/rust_comp_a/src/main.rs @@ -0,0 +1,12 @@ +use a::foo::foo::resources; + +mod a; + +fn main() { + { + let obj = resources::R::new(5); + obj.add(2); + } + let obj2 = resources::create(); + resources::consume(obj2); +} diff --git a/crates/cpp/tests/meshless_resources/rust_comp_b/.gitignore b/crates/cpp/tests/meshless_resources/rust_comp_b/.gitignore new file mode 100644 index 000000000..ea8c4bf7f --- /dev/null +++ b/crates/cpp/tests/meshless_resources/rust_comp_b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/crates/cpp/tests/meshless_resources/rust_comp_b/Cargo.lock b/crates/cpp/tests/meshless_resources/rust_comp_b/Cargo.lock new file mode 100644 index 000000000..1f246830b --- /dev/null +++ b/crates/cpp/tests/meshless_resources/rust_comp_b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "rust_comp_b" +version = "0.1.0" diff --git a/crates/cpp/tests/meshless_resources/rust_comp_b/Cargo.toml b/crates/cpp/tests/meshless_resources/rust_comp_b/Cargo.toml new file mode 100644 index 000000000..a6f9e5ff0 --- /dev/null +++ b/crates/cpp/tests/meshless_resources/rust_comp_b/Cargo.toml @@ -0,0 +1,13 @@ +[workspace] + +[package] +name = "rust_comp_b" +version = "0.1.0" +edition = "2021" + +[lib] +name = "rust_comp_b" +crate-type = ["cdylib"] +#crate-type = ["staticlib", "rlib"] + +[dependencies] diff --git a/crates/cpp/tests/meshless_resources/rust_comp_b/Makefile b/crates/cpp/tests/meshless_resources/rust_comp_b/Makefile new file mode 100644 index 000000000..49ea14e22 --- /dev/null +++ b/crates/cpp/tests/meshless_resources/rust_comp_b/Makefile @@ -0,0 +1,3 @@ + +bindgen: + (cd src; ../../../../../../target/debug/wit-bindgen rust ../../wit/resources_simple.wit -w b --with foo:foo/resources=generate --wasm64) diff --git a/crates/cpp/tests/meshless_resources/rust_comp_b/src/b.rs b/crates/cpp/tests/meshless_resources/rust_comp_b/src/b.rs new file mode 100644 index 000000000..77eb00084 --- /dev/null +++ b/crates/cpp/tests/meshless_resources/rust_comp_b/src/b.rs @@ -0,0 +1,355 @@ +#[allow(dead_code)] +pub mod exports { + #[allow(dead_code)] + pub mod foo { + #[allow(dead_code)] + pub mod foo { + #[allow(dead_code, clippy::all)] + pub mod resources { + #[used] + #[doc(hidden)] + #[cfg(target_arch = "wasm32")] + static __FORCE_SECTION_REF: fn() = + super::super::super::super::__link_custom_section_describing_imports; + use super::super::super::super::_rt; + #[derive(Debug)] + #[repr(transparent)] + pub struct R { + handle: _rt::Resource, + } + type _RRep = Option; + impl R { + /// Creates a new resource from the specified representation. + /// + /// This function will create a new resource handle by moving `val` onto + /// the heap and then passing that heap pointer to the component model to + /// create a handle. The owned handle is then returned as `R`. + pub fn new(val: T) -> Self { + Self::type_guard::(); + let val: _RRep = Some(val); + let ptr: *mut _RRep = _rt::Box::into_raw(_rt::Box::new(val)); + unsafe { Self::from_handle(T::_resource_new(ptr.cast())) } + } + /// Gets access to the underlying `T` which represents this resource. + pub fn get(&self) -> &T { + let ptr = unsafe { &*self.as_ptr::() }; + ptr.as_ref().unwrap() + } + /// Gets mutable access to the underlying `T` which represents this + /// resource. + pub fn get_mut(&mut self) -> &mut T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.as_mut().unwrap() + } + /// Consumes this resource and returns the underlying `T`. + pub fn into_inner(self) -> T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.take().unwrap() + } + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + Self { + handle: _rt::Resource::from_handle(handle), + } + } + #[doc(hidden)] + pub fn take_handle(&self) -> usize { + _rt::Resource::take_handle(&self.handle) + } + #[doc(hidden)] + pub fn handle(&self) -> usize { + _rt::Resource::handle(&self.handle) + } + #[doc(hidden)] + fn type_guard() { + use core::any::TypeId; + static mut LAST_TYPE: Option = None; + unsafe { + assert!(!cfg!(target_feature = "threads")); + let id = TypeId::of::(); + match LAST_TYPE { + Some(ty) => { + assert!( + ty == id, + "cannot use two types with this resource type" + ) + } + None => LAST_TYPE = Some(id), + } + } + } + #[doc(hidden)] + pub unsafe fn dtor(handle: *mut u8) { + Self::type_guard::(); + let _ = _rt::Box::from_raw(handle as *mut _RRep); + } + fn as_ptr(&self) -> *mut _RRep { + R::type_guard::(); + T::_resource_rep(self.handle()).cast() + } + } + /// A borrowed version of [`R`] which represents a borrowed value + /// with the lifetime `'a`. + #[derive(Debug)] + #[repr(transparent)] + pub struct RBorrow<'a> { + rep: *mut u8, + _marker: core::marker::PhantomData<&'a R>, + } + impl<'a> RBorrow<'a> { + #[doc(hidden)] + pub unsafe fn lift(rep: usize) -> Self { + Self { + rep: rep as *mut u8, + _marker: core::marker::PhantomData, + } + } + /// Gets access to the underlying `T` in this resource. + pub fn get(&self) -> &T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.as_ref().unwrap() + } + fn as_ptr(&self) -> *mut _RRep { + R::type_guard::(); + self.rep.cast() + } + } + unsafe impl _rt::WasmResource for R { + #[inline] + unsafe fn drop(_handle: usize) { + { + #[link(wasm_import_module = "foo:foo/resources")] + extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "[resource-drop]r")] + fn fooX3AfooX2FresourcesX00X5Bresource_dropX5Dr(_: usize); + } + fooX3AfooX2FresourcesX00X5Bresource_dropX5Dr(_handle); + } + } + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_constructor_r_cabi(arg0: i32) -> *mut u8 { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let result0 = R::new(T::new(arg0 as u32)); + (result0).take_handle() as *mut u8 + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_method_r_add_cabi(arg0: *mut u8, arg1: i32) { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + T::add(RBorrow::lift(arg0 as usize).get(), arg1 as u32); + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_create_cabi() -> *mut u8 { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let result0 = T::create(); + (result0).take_handle() as *mut u8 + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_consume_cabi(arg0: *mut u8) { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + T::consume(R::from_handle(arg0 as usize)); + } + pub trait Guest { + type R: GuestR; + fn create() -> R; + /// borrows: func(o: borrow); + fn consume(o: R) -> (); + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_drop_r_cabi(arg0: usize) { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + R::dtor::(arg0 as *mut u8); + } + pub trait GuestR: 'static { + #[doc(hidden)] + unsafe fn _resource_new(val: *mut u8) -> usize + where + Self: Sized, + { + val as usize + } + #[doc(hidden)] + fn _resource_rep(handle: usize) -> *mut u8 + where + Self: Sized, + { + handle as *mut u8 + } + fn new(a: u32) -> Self; + fn add(&self, b: u32) -> (); + } + #[doc(hidden)] + macro_rules! __export_foo_foo_resources_cabi { + ($ty:ident with_types_in $($path_to_types:tt)*) => { + const _ : () = { #[cfg_attr(target_arch = "wasm32", export_name = + "[constructor]r")] #[cfg_attr(not(target_arch = "wasm32"), + no_mangle)] unsafe extern "C" fn + fooX3AfooX2FresourcesX00X5BconstructorX5Dr(arg0 : i32,) -> * mut + u8 { $($path_to_types)*:: _export_constructor_r_cabi::<<$ty as + $($path_to_types)*:: Guest >::R > (arg0) } #[cfg_attr(target_arch + = "wasm32", export_name = "[method]r.add")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] unsafe extern + "C" fn fooX3AfooX2FresourcesX00X5BmethodX5DrX2Eadd(arg0 : * mut + u8, arg1 : i32,) { $($path_to_types)*:: + _export_method_r_add_cabi::<<$ty as $($path_to_types)*:: Guest + >::R > (arg0, arg1) } #[cfg_attr(target_arch = "wasm32", + export_name = "create")] #[cfg_attr(not(target_arch = "wasm32"), + no_mangle)] unsafe extern "C" fn fooX3AfooX2FresourcesX00create() + -> * mut u8 { $($path_to_types)*:: _export_create_cabi::<$ty > () + } #[cfg_attr(target_arch = "wasm32", export_name = "consume")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] unsafe extern + "C" fn fooX3AfooX2FresourcesX00consume(arg0 : * mut u8,) { + $($path_to_types)*:: _export_consume_cabi::<$ty > (arg0) } + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] unsafe extern + "C" fn fooX3AfooX2FresourcesX00X5Bresource_dropX5Dr(arg0 : usize) + { $($path_to_types)*:: _export_drop_r_cabi::<<$ty as + $($path_to_types)*:: Guest >::R > (arg0) } }; + }; + } + #[doc(hidden)] + pub(crate) use __export_foo_foo_resources_cabi; + } + } + } +} +mod _rt { + use core::fmt; + use core::marker; + use core::sync::atomic::{AtomicUsize, Ordering::Relaxed}; + /// A type which represents a component model resource, either imported or + /// exported into this component. + /// + /// This is a low-level wrapper which handles the lifetime of the resource + /// (namely this has a destructor). The `T` provided defines the component model + /// intrinsics that this wrapper uses. + /// + /// One of the chief purposes of this type is to provide `Deref` implementations + /// to access the underlying data when it is owned. + /// + /// This type is primarily used in generated code for exported and imported + /// resources. + #[repr(transparent)] + pub struct Resource { + handle: AtomicUsize, + _marker: marker::PhantomData, + } + /// A trait which all wasm resources implement, namely providing the ability to + /// drop a resource. + /// + /// This generally is implemented by generated code, not user-facing code. + #[allow(clippy::missing_safety_doc)] + pub unsafe trait WasmResource { + /// Invokes the `[resource-drop]...` intrinsic. + unsafe fn drop(handle: usize); + } + impl Resource { + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + debug_assert!(handle != 0); + Self { + handle: AtomicUsize::new(handle), + _marker: marker::PhantomData, + } + } + /// Takes ownership of the handle owned by `resource`. + /// + /// Note that this ideally would be `into_handle` taking `Resource` by + /// ownership. The code generator does not enable that in all situations, + /// unfortunately, so this is provided instead. + /// + /// Also note that `take_handle` is in theory only ever called on values + /// owned by a generated function. For example a generated function might + /// take `Resource` as an argument but then call `take_handle` on a + /// reference to that argument. In that sense the dynamic nature of + /// `take_handle` should only be exposed internally to generated code, not + /// to user code. + #[doc(hidden)] + pub fn take_handle(resource: &Resource) -> usize { + resource.handle.swap(0, Relaxed) + } + #[doc(hidden)] + pub fn handle(resource: &Resource) -> usize { + resource.handle.load(Relaxed) + } + } + impl fmt::Debug for Resource { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Resource") + .field("handle", &self.handle) + .finish() + } + } + impl Drop for Resource { + fn drop(&mut self) { + unsafe { + match self.handle.load(Relaxed) { + 0 => {} + other => T::drop(other), + } + } + } + } + pub use alloc_crate::boxed::Box; + #[cfg(target_arch = "wasm32")] + pub fn run_ctors_once() { + wit_bindgen::rt::run_ctors_once(); + } + extern crate alloc as alloc_crate; +} +/// Generates `#[no_mangle]` functions to export the specified type as the +/// root implementation of all generated traits. +/// +/// For more information see the documentation of `wit_bindgen::generate!`. +/// +/// ```rust +/// # macro_rules! export{ ($($t:tt)*) => (); } +/// # trait Guest {} +/// struct MyType; +/// +/// impl Guest for MyType { +/// // ... +/// } +/// +/// export!(MyType); +/// ``` +#[allow(unused_macros)] +#[doc(hidden)] +macro_rules! __export_b_impl { + ($ty:ident) => { + self::export!($ty with_types_in self); + }; + ($ty:ident with_types_in $($path_to_types_root:tt)*) => { + $($path_to_types_root)*:: + exports::foo::foo::resources::__export_foo_foo_resources_cabi!($ty with_types_in + $($path_to_types_root)*:: exports::foo::foo::resources); + }; +} +#[doc(inline)] +pub(crate) use __export_b_impl as export; +#[cfg(target_arch = "wasm32")] +#[link_section = "component-type:wit-bindgen:0.28.0:b:encoded world"] +#[doc(hidden)] +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 272] = *b"\ +\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\x98\x01\x01A\x02\x01\ +A\x02\x01B\x0b\x04\0\x01r\x03\x01\x01i\0\x01@\x01\x01ay\0\x01\x04\0\x0e[construc\ +tor]r\x01\x02\x01h\0\x01@\x02\x04self\x03\x01by\x01\0\x04\0\x0d[method]r.add\x01\ +\x04\x01@\0\0\x01\x04\0\x06create\x01\x05\x01@\x01\x01o\x01\x01\0\x04\0\x07consu\ +me\x01\x06\x04\x01\x11foo:foo/resources\x05\0\x04\x01\x09foo:foo/b\x04\0\x0b\x07\ +\x01\0\x01b\x03\0\0\0G\x09producers\x01\x0cprocessed-by\x02\x0dwit-component\x07\ +0.214.0\x10wit-bindgen-rust\x060.28.0"; +#[inline(never)] +#[doc(hidden)] +#[cfg(target_arch = "wasm32")] +pub fn __link_custom_section_describing_imports() { + wit_bindgen::rt::maybe_link_cabi_realloc(); +} diff --git a/crates/cpp/tests/meshless_resources/rust_comp_b/src/lib.rs b/crates/cpp/tests/meshless_resources/rust_comp_b/src/lib.rs new file mode 100644 index 000000000..eb353f5ea --- /dev/null +++ b/crates/cpp/tests/meshless_resources/rust_comp_b/src/lib.rs @@ -0,0 +1,37 @@ +use std::sync::Mutex; + +use b::exports::foo::foo::resources::{self, Guest, GuestR}; + +mod b; + +b::export!(MyWorld with_types_in b); + +#[derive(Debug)] +struct MyResource(Mutex); + +impl GuestR for MyResource { + fn new(a: u32) -> Self { + MyResource(Mutex::new(a)) + } + + fn add(&self, b: u32) { + *self.0.lock().unwrap() += b; + } +} + +struct MyWorld; + +impl Guest for MyWorld { + type R = MyResource; + + fn create() -> resources::R { + resources::R::new(MyResource::new(17)) + } + + fn consume(o: resources::R) { + println!( + "resource consumed with {:?}", + o.get::().0.lock().unwrap() + ); + } +} diff --git a/crates/cpp/tests/meshless_resources/wit/resources_simple.wit b/crates/cpp/tests/meshless_resources/wit/resources_simple.wit new file mode 120000 index 000000000..b1b09c3ba --- /dev/null +++ b/crates/cpp/tests/meshless_resources/wit/resources_simple.wit @@ -0,0 +1 @@ +../../native_mesh/wit/resources_simple.wit \ No newline at end of file diff --git a/crates/cpp/tests/meshless_strings/.gitignore b/crates/cpp/tests/meshless_strings/.gitignore new file mode 100644 index 000000000..aef20e490 --- /dev/null +++ b/crates/cpp/tests/meshless_strings/.gitignore @@ -0,0 +1,2 @@ +/component_a/component_a +/component_b/libcomponent_b.a diff --git a/crates/cpp/tests/meshless_strings/Makefile b/crates/cpp/tests/meshless_strings/Makefile new file mode 120000 index 000000000..7951ca18d --- /dev/null +++ b/crates/cpp/tests/meshless_strings/Makefile @@ -0,0 +1 @@ +../meshless_resources/Makefile \ No newline at end of file diff --git a/crates/cpp/tests/meshless_strings/component_a/Makefile b/crates/cpp/tests/meshless_strings/component_a/Makefile new file mode 100644 index 000000000..1b0496e00 --- /dev/null +++ b/crates/cpp/tests/meshless_strings/component_a/Makefile @@ -0,0 +1,12 @@ +CXXFLAGS=-g -O0 -I../../../helper-types + +all: component_a + +component_a: the_world.cpp main.cpp + $(CXX) $(CXXFLAGS) -o $@ $^ -L../component_b -lcomponent_b + +bindgen: + ../../../../../target/debug/wit-bindgen cpp ../wit --symmetric --internal-prefix=comp_a --new_api --format + +clean: + -rm *~ component_a *.o diff --git a/crates/cpp/tests/meshless_strings/component_a/main.cpp b/crates/cpp/tests/meshless_strings/component_a/main.cpp new file mode 100644 index 000000000..03e6d1448 --- /dev/null +++ b/crates/cpp/tests/meshless_strings/component_a/main.cpp @@ -0,0 +1,32 @@ + +#include "the_world_cpp.h" +#include + +void comp_a::exports::foo::foo::strings::A(std::string_view x) { + std::cout << x << std::endl; +} +wit::string comp_a::exports::foo::foo::strings::B() { + wit::string b = wit::string::from_view(std::string_view("hello B")); + return b; +} +wit::string comp_a::exports::foo::foo::strings::C(std::string_view a, + std::string_view b) { + std::cout << a << '|' << b << std::endl; + wit::string c = wit::string::from_view(std::string_view("hello C")); + return c; +} + +int main() { + comp_a::foo::foo::strings::A(std::string_view("hello A")); + + { + auto b = comp_a::foo::foo::strings::B(); + std::cout << b.get_view() << std::endl; + // make sure that b's result is destructed before calling C + } + + auto c = comp_a::foo::foo::strings::C(std::string_view("hello C1"), + std::string_view("hello C2")); + std::cout << c.get_view() << std::endl; + return 0; +} diff --git a/crates/cpp/tests/meshless_strings/component_a/the_world.cpp b/crates/cpp/tests/meshless_strings/component_a/the_world.cpp new file mode 100644 index 000000000..0ac6756d3 --- /dev/null +++ b/crates/cpp/tests/meshless_strings/component_a/the_world.cpp @@ -0,0 +1,105 @@ +// Generated by `wit-bindgen` 0.3.0. DO NOT EDIT! + +// Ensure that the *_component_type.o object is linked in +#ifdef __wasm32__ +extern void __component_type_object_force_link_the_world(void); +void __component_type_object_force_link_the_world_public_use_in_this_compilation_unit( + void) { + __component_type_object_force_link_the_world(); +} +#endif +#include "the_world_cpp.h" +#include // realloc + +extern "C" void *cabi_realloc(void *ptr, size_t old_size, size_t align, + size_t new_size); + +__attribute__((__weak__, __export_name__("cabi_realloc"))) void * +cabi_realloc(void *ptr, size_t old_size, size_t align, size_t new_size) { + (void)old_size; + if (new_size == 0) + return (void *)align; + void *ret = realloc(ptr, new_size); + if (!ret) + abort(); + return ret; +} + +extern "C" __attribute__((import_module("foo:foo/strings"))) +__attribute__((import_name("a"))) void +fooX3AfooX2FstringsX00a(uint8_t *, size_t); +extern "C" __attribute__((import_module("foo:foo/strings"))) +__attribute__((import_name("b"))) void +fooX3AfooX2FstringsX00b(uint8_t *); +extern "C" __attribute__((import_module("foo:foo/strings"))) +__attribute__((import_name("c"))) void +fooX3AfooX2FstringsX00c(uint8_t *, size_t, uint8_t *, size_t, uint8_t *); +void comp_a::foo::foo::strings::A(std::string_view x) { + auto const &vec0 = x; + auto ptr0 = (uint8_t *)(vec0.data()); + auto len0 = (size_t)(vec0.size()); + fooX3AfooX2FstringsX00a(ptr0, len0); +} +wit::string comp_a::foo::foo::strings::B() { + uintptr_t ret_area[((2 * sizeof(void *)) + sizeof(uintptr_t) - 1) / + sizeof(uintptr_t)]; + uint8_t *ptr0 = (uint8_t *)(&ret_area); + fooX3AfooX2FstringsX00b(ptr0); + auto len1 = *((size_t *)(ptr0 + sizeof(void *))); + + return wit::string((char const *)(*((uint8_t **)(ptr0 + 0))), len1); +} +wit::string comp_a::foo::foo::strings::C(std::string_view a, + std::string_view b) { + auto const &vec0 = a; + auto ptr0 = (uint8_t *)(vec0.data()); + auto len0 = (size_t)(vec0.size()); + auto const &vec1 = b; + auto ptr1 = (uint8_t *)(vec1.data()); + auto len1 = (size_t)(vec1.size()); + uintptr_t ret_area[((2 * sizeof(void *)) + sizeof(uintptr_t) - 1) / + sizeof(uintptr_t)]; + uint8_t *ptr2 = (uint8_t *)(&ret_area); + fooX3AfooX2FstringsX00c(ptr0, len0, ptr1, len1, ptr2); + auto len3 = *((size_t *)(ptr2 + sizeof(void *))); + + return wit::string((char const *)(*((uint8_t **)(ptr2 + 0))), len3); +} +extern "C" __attribute__((__export_name__("foo:foo/strings#a"))) void +a_fooX3AfooX2FstringsX00a(uint8_t *arg0, size_t arg1) { + auto len0 = arg1; + + comp_a::exports::foo::foo::strings::A( + std::string_view((char const *)(arg0), len0)); +} +extern "C" __attribute__((__export_name__("foo:foo/strings#b"))) void +a_fooX3AfooX2FstringsX00b(uint8_t *arg0) { + auto result0 = comp_a::exports::foo::foo::strings::B(); + auto const &vec1 = result0; + auto ptr1 = (uint8_t *)(vec1.data()); + auto len1 = (size_t)(vec1.size()); + result0.leak(); + + *((size_t *)(arg0 + sizeof(void *))) = len1; + *((uint8_t **)(arg0 + 0)) = ptr1; +} +extern "C" __attribute__((__export_name__("foo:foo/strings#c"))) void +a_fooX3AfooX2FstringsX00c(uint8_t *arg0, size_t arg1, uint8_t *arg2, size_t arg3, + uint8_t *arg4) { + auto len0 = arg1; + + auto len1 = arg3; + + auto result2 = comp_a::exports::foo::foo::strings::C( + std::string_view((char const *)(arg0), len0), + std::string_view((char const *)(arg2), len1)); + auto const &vec3 = result2; + auto ptr3 = (uint8_t *)(vec3.data()); + auto len3 = (size_t)(vec3.size()); + result2.leak(); + + *((size_t *)(arg4 + sizeof(void *))) = len3; + *((uint8_t **)(arg4 + 0)) = ptr3; +} + +// Component Adapters diff --git a/crates/cpp/tests/meshless_strings/component_a/the_world_cpp.h b/crates/cpp/tests/meshless_strings/component_a/the_world_cpp.h new file mode 100644 index 000000000..019af892c --- /dev/null +++ b/crates/cpp/tests/meshless_strings/component_a/the_world_cpp.h @@ -0,0 +1,33 @@ +// Generated by `wit-bindgen` 0.3.0. DO NOT EDIT! +#ifndef __CPP_GUEST_BINDINGS_THE_WORLD_H +#define __CPP_GUEST_BINDINGS_THE_WORLD_H +#define WIT_SYMMETRIC +#include +#include +#include +#include +namespace comp_a { +namespace foo { +namespace foo { +namespace strings { +void A(std::string_view x); +wit::string B(); +wit::string C(std::string_view a, std::string_view b); +// export_interface Interface(Id { idx: 0 }) +} // namespace strings +} // namespace foo +} // namespace foo +namespace exports { +namespace foo { +namespace foo { +namespace strings { +void A(std::string_view x); +wit::string B(); +wit::string C(std::string_view a, std::string_view b); +} // namespace strings +} // namespace foo +} // namespace foo +} // namespace exports +} // namespace comp_a + +#endif diff --git a/crates/cpp/tests/meshless_strings/component_b/Makefile b/crates/cpp/tests/meshless_strings/component_b/Makefile new file mode 100644 index 000000000..917578e1e --- /dev/null +++ b/crates/cpp/tests/meshless_strings/component_b/Makefile @@ -0,0 +1,12 @@ +CXXFLAGS=-g -O0 -I../../../helper-types + +all: libcomponent_b.a + +libcomponent_b.a: the_world.o guest.o + ar rcvs $@ $^ + +bindgen: + ../../../../../target/debug/wit-bindgen cpp ../wit --symmetric --new-api --format + +clean: + -rm *~ *.a *.o diff --git a/crates/cpp/tests/meshless_strings/component_b/guest.cpp b/crates/cpp/tests/meshless_strings/component_b/guest.cpp new file mode 100644 index 000000000..e357dbbd3 --- /dev/null +++ b/crates/cpp/tests/meshless_strings/component_b/guest.cpp @@ -0,0 +1,13 @@ +#include "the_world_cpp.h" + +void exports::foo::foo::strings::A(std::string_view x) { + ::foo::foo::strings::A(x); +} + +wit::string exports::foo::foo::strings::B() { + return ::foo::foo::strings::B(); +} + +wit::string exports::foo::foo::strings::C(std::string_view x, std::string_view b) { + return ::foo::foo::strings::C(x, b); +} diff --git a/crates/cpp/tests/meshless_strings/component_b/the_world.cpp b/crates/cpp/tests/meshless_strings/component_b/the_world.cpp new file mode 100644 index 000000000..713810a61 --- /dev/null +++ b/crates/cpp/tests/meshless_strings/component_b/the_world.cpp @@ -0,0 +1,103 @@ +// Generated by `wit-bindgen` 0.3.0. DO NOT EDIT! + +// Ensure that the *_component_type.o object is linked in +#ifdef __wasm32__ +extern void __component_type_object_force_link_the_world(void); +void __component_type_object_force_link_the_world_public_use_in_this_compilation_unit( + void) { + __component_type_object_force_link_the_world(); +} +#endif +#include "the_world_cpp.h" +#include // realloc + +extern "C" void *cabi_realloc(void *ptr, size_t old_size, size_t align, + size_t new_size); + +__attribute__((__weak__, __export_name__("cabi_realloc"))) void * +cabi_realloc(void *ptr, size_t old_size, size_t align, size_t new_size) { + (void)old_size; + if (new_size == 0) + return (void *)align; + void *ret = realloc(ptr, new_size); + if (!ret) + abort(); + return ret; +} + +extern "C" __attribute__((import_module("foo:foo/strings"))) +__attribute__((import_name("a"))) void +a_fooX3AfooX2FstringsX00a(uint8_t *, size_t); +extern "C" __attribute__((import_module("foo:foo/strings"))) +__attribute__((import_name("b"))) void +a_fooX3AfooX2FstringsX00b(uint8_t *); +extern "C" __attribute__((import_module("foo:foo/strings"))) +__attribute__((import_name("c"))) void +a_fooX3AfooX2FstringsX00c(uint8_t *, size_t, uint8_t *, size_t, uint8_t *); +void foo::foo::strings::A(std::string_view x) { + auto const &vec0 = x; + auto ptr0 = (uint8_t *)(vec0.data()); + auto len0 = (size_t)(vec0.size()); + a_fooX3AfooX2FstringsX00a(ptr0, len0); +} +wit::string foo::foo::strings::B() { + uintptr_t ret_area[((2 * sizeof(void *)) + sizeof(uintptr_t) - 1) / + sizeof(uintptr_t)]; + uint8_t *ptr0 = (uint8_t *)(&ret_area); + a_fooX3AfooX2FstringsX00b(ptr0); + auto len1 = *((size_t *)(ptr0 + sizeof(void *))); + + return wit::string((char const *)(*((uint8_t **)(ptr0 + 0))), len1); +} +wit::string foo::foo::strings::C(std::string_view a, std::string_view b) { + auto const &vec0 = a; + auto ptr0 = (uint8_t *)(vec0.data()); + auto len0 = (size_t)(vec0.size()); + auto const &vec1 = b; + auto ptr1 = (uint8_t *)(vec1.data()); + auto len1 = (size_t)(vec1.size()); + uintptr_t ret_area[((2 * sizeof(void *)) + sizeof(uintptr_t) - 1) / + sizeof(uintptr_t)]; + uint8_t *ptr2 = (uint8_t *)(&ret_area); + a_fooX3AfooX2FstringsX00c(ptr0, len0, ptr1, len1, ptr2); + auto len3 = *((size_t *)(ptr2 + sizeof(void *))); + + return wit::string((char const *)(*((uint8_t **)(ptr2 + 0))), len3); +} +extern "C" __attribute__((__export_name__("foo:foo/strings#a"))) void +fooX3AfooX2FstringsX00a(uint8_t *arg0, size_t arg1) { + auto len0 = arg1; + + exports::foo::foo::strings::A(std::string_view((char const *)(arg0), len0)); +} +extern "C" __attribute__((__export_name__("foo:foo/strings#b"))) void +fooX3AfooX2FstringsX00b(uint8_t *arg0) { + auto result0 = exports::foo::foo::strings::B(); + auto const &vec1 = result0; + auto ptr1 = (uint8_t *)(vec1.data()); + auto len1 = (size_t)(vec1.size()); + result0.leak(); + + *((size_t *)(arg0 + sizeof(void *))) = len1; + *((uint8_t **)(arg0 + 0)) = ptr1; +} +extern "C" __attribute__((__export_name__("foo:foo/strings#c"))) void +fooX3AfooX2FstringsX00c(uint8_t *arg0, size_t arg1, uint8_t *arg2, size_t arg3, + uint8_t *arg4) { + auto len0 = arg1; + + auto len1 = arg3; + + auto result2 = exports::foo::foo::strings::C( + std::string_view((char const *)(arg0), len0), + std::string_view((char const *)(arg2), len1)); + auto const &vec3 = result2; + auto ptr3 = (uint8_t *)(vec3.data()); + auto len3 = (size_t)(vec3.size()); + result2.leak(); + + *((size_t *)(arg4 + sizeof(void *))) = len3; + *((uint8_t **)(arg4 + 0)) = ptr3; +} + +// Component Adapters diff --git a/crates/cpp/tests/meshless_strings/component_b/the_world_cpp.h b/crates/cpp/tests/meshless_strings/component_b/the_world_cpp.h new file mode 100644 index 000000000..718f3ddfd --- /dev/null +++ b/crates/cpp/tests/meshless_strings/component_b/the_world_cpp.h @@ -0,0 +1,31 @@ +// Generated by `wit-bindgen` 0.3.0. DO NOT EDIT! +#ifndef __CPP_GUEST_BINDINGS_THE_WORLD_H +#define __CPP_GUEST_BINDINGS_THE_WORLD_H +#define WIT_SYMMETRIC +#include +#include +#include +#include +namespace foo { +namespace foo { +namespace strings { +void A(std::string_view x); +wit::string B(); +wit::string C(std::string_view a, std::string_view b); +// export_interface Interface(Id { idx: 0 }) +} // namespace strings +} // namespace foo +} // namespace foo +namespace exports { +namespace foo { +namespace foo { +namespace strings { +void A(std::string_view x); +wit::string B(); +wit::string C(std::string_view a, std::string_view b); +} // namespace strings +} // namespace foo +} // namespace foo +} // namespace exports + +#endif diff --git a/crates/cpp/tests/meshless_strings/rust_comp_a/.gitignore b/crates/cpp/tests/meshless_strings/rust_comp_a/.gitignore new file mode 100644 index 000000000..ea8c4bf7f --- /dev/null +++ b/crates/cpp/tests/meshless_strings/rust_comp_a/.gitignore @@ -0,0 +1 @@ +/target diff --git a/crates/cpp/tests/meshless_strings/rust_comp_a/Cargo.lock b/crates/cpp/tests/meshless_strings/rust_comp_a/Cargo.lock new file mode 100644 index 000000000..b4bb8f1be --- /dev/null +++ b/crates/cpp/tests/meshless_strings/rust_comp_a/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "rust_comp_a" +version = "0.1.0" diff --git a/crates/cpp/tests/meshless_strings/rust_comp_a/Cargo.toml b/crates/cpp/tests/meshless_strings/rust_comp_a/Cargo.toml new file mode 100644 index 000000000..dd669d8a6 --- /dev/null +++ b/crates/cpp/tests/meshless_strings/rust_comp_a/Cargo.toml @@ -0,0 +1,8 @@ +[workspace] + +[package] +name = "rust_comp_a" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/crates/cpp/tests/meshless_strings/rust_comp_a/build.rs b/crates/cpp/tests/meshless_strings/rust_comp_a/build.rs new file mode 100644 index 000000000..f6026a6f7 --- /dev/null +++ b/crates/cpp/tests/meshless_strings/rust_comp_a/build.rs @@ -0,0 +1,11 @@ +use std::env; + +fn main() { + let source_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + println!( + "cargo::rustc-link-search=native={}/../component_b", + source_dir + ); + println!("cargo::rustc-link-lib=static=component_b"); + println!("cargo::rustc-link-lib=static=stdc++"); +} diff --git a/crates/cpp/tests/meshless_strings/rust_comp_a/src/main.rs b/crates/cpp/tests/meshless_strings/rust_comp_a/src/main.rs new file mode 100644 index 000000000..c211223fe --- /dev/null +++ b/crates/cpp/tests/meshless_strings/rust_comp_a/src/main.rs @@ -0,0 +1,33 @@ +use the_world::exports::foo::foo::strings::Guest; +use the_world::foo::foo::strings; + +mod the_world; + +struct MyWorld; + +impl Guest for MyWorld { + fn a(x: String) { + println!("{x}"); + } + + fn b() -> String { + String::from("hello B") + } + + fn c(a: String, b: String) -> String { + println!("{a}|{b}"); + "hello C".into() + } +} + +the_world::export!(MyWorld with_types_in the_world); + +fn main() { + strings::a("hello A"); + { + let b = strings::b(); + println!("{b}"); + } + let c = strings::c("hello C1", "hello C2"); + println!("{c}"); +} diff --git a/crates/cpp/tests/meshless_strings/rust_comp_a/src/the_world.rs b/crates/cpp/tests/meshless_strings/rust_comp_a/src/the_world.rs new file mode 100644 index 000000000..a2ff06f4f --- /dev/null +++ b/crates/cpp/tests/meshless_strings/rust_comp_a/src/the_world.rs @@ -0,0 +1,263 @@ +#[allow(dead_code)] +pub mod foo { + #[allow(dead_code)] + pub mod foo { + #[allow(dead_code, clippy::all)] + pub mod strings { + #[used] + #[doc(hidden)] + #[cfg(target_arch = "wasm32")] + static __FORCE_SECTION_REF: fn() = super::super::super::__link_custom_section_describing_imports; + use super::super::super::_rt; + #[allow(unused_unsafe, clippy::all)] + pub fn a(x: &str) -> () { + unsafe { + let vec0 = x; + let ptr0 = vec0.as_ptr().cast::(); + let len0 = vec0.len(); + #[link(wasm_import_module = "foo:foo/strings")] + extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "a")] + fn fooX3AfooX2FstringsX00a(_: *mut u8, _: usize); + } + fooX3AfooX2FstringsX00a(ptr0.cast_mut(), len0); + } + } + #[allow(unused_unsafe, clippy::all)] + pub fn b() -> _rt::String { + unsafe { + #[cfg_attr(target_pointer_width = "64", repr(align(8)))] + #[cfg_attr(target_pointer_width = "32", repr(align(4)))] + struct RetArea( + [::core::mem::MaybeUninit< + u8, + >; (2 * core::mem::size_of::<*const u8>())], + ); + let mut ret_area = RetArea( + [::core::mem::MaybeUninit::uninit(); (2 + * core::mem::size_of::<*const u8>())], + ); + let ptr0 = ret_area.0.as_mut_ptr().cast::(); + #[link(wasm_import_module = "foo:foo/strings")] + extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "b")] + fn fooX3AfooX2FstringsX00b(_: *mut u8); + } + fooX3AfooX2FstringsX00b(ptr0); + let l1 = *ptr0.add(0).cast::<*mut u8>(); + let l2 = *ptr0 + .add(core::mem::size_of::<*const u8>()) + .cast::(); + let len3 = l2; + let bytes3 = _rt::Vec::from_raw_parts(l1.cast(), len3, len3); + _rt::string_lift(bytes3) + } + } + #[allow(unused_unsafe, clippy::all)] + pub fn c(a: &str, b: &str) -> _rt::String { + unsafe { + #[cfg_attr(target_pointer_width = "64", repr(align(8)))] + #[cfg_attr(target_pointer_width = "32", repr(align(4)))] + struct RetArea( + [::core::mem::MaybeUninit< + u8, + >; (2 * core::mem::size_of::<*const u8>())], + ); + let mut ret_area = RetArea( + [::core::mem::MaybeUninit::uninit(); (2 + * core::mem::size_of::<*const u8>())], + ); + let vec0 = a; + let ptr0 = vec0.as_ptr().cast::(); + let len0 = vec0.len(); + let vec1 = b; + let ptr1 = vec1.as_ptr().cast::(); + let len1 = vec1.len(); + let ptr2 = ret_area.0.as_mut_ptr().cast::(); + #[link(wasm_import_module = "foo:foo/strings")] + extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "c")] + fn fooX3AfooX2FstringsX00c( + _: *mut u8, + _: usize, + _: *mut u8, + _: usize, + _: *mut u8, + ); + } + fooX3AfooX2FstringsX00c( + ptr0.cast_mut(), + len0, + ptr1.cast_mut(), + len1, + ptr2, + ); + let l3 = *ptr2.add(0).cast::<*mut u8>(); + let l4 = *ptr2 + .add(core::mem::size_of::<*const u8>()) + .cast::(); + let len5 = l4; + let bytes5 = _rt::Vec::from_raw_parts(l3.cast(), len5, len5); + _rt::string_lift(bytes5) + } + } + } + } +} +#[allow(dead_code)] +pub mod exports { + #[allow(dead_code)] + pub mod foo { + #[allow(dead_code)] + pub mod foo { + #[allow(dead_code, clippy::all)] + pub mod strings { + #[used] + #[doc(hidden)] + #[cfg(target_arch = "wasm32")] + static __FORCE_SECTION_REF: fn() = super::super::super::super::__link_custom_section_describing_imports; + use super::super::super::super::_rt; + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_a_cabi(arg0: *mut u8, arg1: usize) { + #[cfg(target_arch = "wasm32")] _rt::run_ctors_once(); + let len0 = arg1; + let string0 = String::from( + std::str::from_utf8(std::slice::from_raw_parts(arg0, len0)) + .unwrap(), + ); + T::a(string0); + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_b_cabi(arg0: *mut u8) { + #[cfg(target_arch = "wasm32")] _rt::run_ctors_once(); + let result0 = T::b(); + let vec1 = (result0.into_bytes()).into_boxed_slice(); + let ptr1 = vec1.as_ptr().cast::(); + let len1 = vec1.len(); + ::core::mem::forget(vec1); + *arg0.add(core::mem::size_of::<*const u8>()).cast::() = len1; + *arg0.add(0).cast::<*mut u8>() = ptr1.cast_mut(); + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_c_cabi( + arg0: *mut u8, + arg1: usize, + arg2: *mut u8, + arg3: usize, + arg4: *mut u8, + ) { + #[cfg(target_arch = "wasm32")] _rt::run_ctors_once(); + let len0 = arg1; + let string0 = String::from( + std::str::from_utf8(std::slice::from_raw_parts(arg0, len0)) + .unwrap(), + ); + let len1 = arg3; + let string1 = String::from( + std::str::from_utf8(std::slice::from_raw_parts(arg2, len1)) + .unwrap(), + ); + let result2 = T::c(string0, string1); + let vec3 = (result2.into_bytes()).into_boxed_slice(); + let ptr3 = vec3.as_ptr().cast::(); + let len3 = vec3.len(); + ::core::mem::forget(vec3); + *arg4.add(core::mem::size_of::<*const u8>()).cast::() = len3; + *arg4.add(0).cast::<*mut u8>() = ptr3.cast_mut(); + } + pub trait Guest { + fn a(x: _rt::String) -> (); + fn b() -> _rt::String; + fn c(a: _rt::String, b: _rt::String) -> _rt::String; + } + #[doc(hidden)] + macro_rules! __export_foo_foo_strings_cabi { + ($ty:ident with_types_in $($path_to_types:tt)*) => { + const _ : () = { #[cfg_attr(target_arch = "wasm32", export_name = + "a")] #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] unsafe + extern "C" fn a_fooX3AfooX2FstringsX00a(arg0 : * mut u8, arg1 : + usize,) { $($path_to_types)*:: _export_a_cabi::<$ty > (arg0, + arg1) } #[cfg_attr(target_arch = "wasm32", export_name = "b")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] unsafe extern + "C" fn a_fooX3AfooX2FstringsX00b(arg0 : * mut u8,) { + $($path_to_types)*:: _export_b_cabi::<$ty > (arg0) } + #[cfg_attr(target_arch = "wasm32", export_name = "c")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] unsafe extern + "C" fn a_fooX3AfooX2FstringsX00c(arg0 : * mut u8, arg1 : usize, + arg2 : * mut u8, arg3 : usize, arg4 : * mut u8,) { + $($path_to_types)*:: _export_c_cabi::<$ty > (arg0, arg1, arg2, + arg3, arg4) } }; + }; + } + #[doc(hidden)] + pub(crate) use __export_foo_foo_strings_cabi; + } + } + } +} +mod _rt { + pub use alloc_crate::string::String; + pub use alloc_crate::vec::Vec; + pub unsafe fn string_lift(bytes: Vec) -> String { + if cfg!(debug_assertions) { + String::from_utf8(bytes).unwrap() + } else { + String::from_utf8_unchecked(bytes) + } + } + #[cfg(target_arch = "wasm32")] + pub fn run_ctors_once() { + wit_bindgen::rt::run_ctors_once(); + } + extern crate alloc as alloc_crate; +} +/// Generates `#[no_mangle]` functions to export the specified type as the +/// root implementation of all generated traits. +/// +/// For more information see the documentation of `wit_bindgen::generate!`. +/// +/// ```rust +/// # macro_rules! export{ ($($t:tt)*) => (); } +/// # trait Guest {} +/// struct MyType; +/// +/// impl Guest for MyType { +/// // ... +/// } +/// +/// export!(MyType); +/// ``` +#[allow(unused_macros)] +#[doc(hidden)] +macro_rules! __export_the_world_impl { + ($ty:ident) => { + self::export!($ty with_types_in self); + }; + ($ty:ident with_types_in $($path_to_types_root:tt)*) => { + $($path_to_types_root)*:: + exports::foo::foo::strings::__export_foo_foo_strings_cabi!($ty with_types_in + $($path_to_types_root)*:: exports::foo::foo::strings); + }; +} +#[doc(inline)] +pub(crate) use __export_the_world_impl as export; +#[cfg(target_arch = "wasm32")] +#[link_section = "component-type:wit-bindgen:0.28.0:the-world:encoded world"] +#[doc(hidden)] +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 286] = *b"\ +\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\x9e\x01\x01A\x02\x01\ +A\x04\x01B\x06\x01@\x01\x01xs\x01\0\x04\0\x01a\x01\0\x01@\0\0s\x04\0\x01b\x01\x01\ +\x01@\x02\x01as\x01bs\0s\x04\0\x01c\x01\x02\x03\x01\x0ffoo:foo/strings\x05\0\x01\ +B\x06\x01@\x01\x01xs\x01\0\x04\0\x01a\x01\0\x01@\0\0s\x04\0\x01b\x01\x01\x01@\x02\ +\x01as\x01bs\0s\x04\0\x01c\x01\x02\x04\x01\x0ffoo:foo/strings\x05\x01\x04\x01\x11\ +foo:foo/the-world\x04\0\x0b\x0f\x01\0\x09the-world\x03\0\0\0G\x09producers\x01\x0c\ +processed-by\x02\x0dwit-component\x070.215.0\x10wit-bindgen-rust\x060.28.0"; +#[inline(never)] +#[doc(hidden)] +#[cfg(target_arch = "wasm32")] +pub fn __link_custom_section_describing_imports() { + wit_bindgen::rt::maybe_link_cabi_realloc(); +} diff --git a/crates/cpp/tests/meshless_strings/wit b/crates/cpp/tests/meshless_strings/wit new file mode 120000 index 000000000..feb12df31 --- /dev/null +++ b/crates/cpp/tests/meshless_strings/wit @@ -0,0 +1 @@ +../native_strings/wit \ No newline at end of file diff --git a/crates/cpp/tests/misc_wit/option_handle.wit b/crates/cpp/tests/misc_wit/option_handle.wit new file mode 100644 index 000000000..38b19ec4e --- /dev/null +++ b/crates/cpp/tests/misc_wit/option_handle.wit @@ -0,0 +1,11 @@ +package test:test; + +interface iface { + resource r; + + myfunc: func() -> option; +} + +world myworld { + import iface; +} diff --git a/crates/cpp/tests/native_mesh/Makefile b/crates/cpp/tests/native_mesh/Makefile new file mode 100644 index 000000000..497019c6d --- /dev/null +++ b/crates/cpp/tests/native_mesh/Makefile @@ -0,0 +1,16 @@ + +all: + make -C component_b $@ + make -C mesh $@ + make -C component_a $@ + +bindgen: + make -C component_b $@ + make -C mesh $@ + make -C component_a $@ + +clean: + make -C component_b $@ + make -C mesh $@ + make -C component_a $@ + \ No newline at end of file diff --git a/crates/cpp/tests/native_mesh/component_a/Makefile b/crates/cpp/tests/native_mesh/component_a/Makefile new file mode 100644 index 000000000..829b6275e --- /dev/null +++ b/crates/cpp/tests/native_mesh/component_a/Makefile @@ -0,0 +1,12 @@ +CXXFLAGS=-g -O0 -I../../../helper-types + +all: component_a + +component_a: a.cpp main.cpp + $(CXX) $(CXXFLAGS) -o $@ $^ libmesh.so libcomponent_b.so + +bindgen: + ../../../../../target/debug/wit-bindgen cpp ../wit -w a --wasm64 --format + +clean: + -rm *~ component_a *.o diff --git a/crates/cpp/tests/native_mesh/component_a/a.cpp b/crates/cpp/tests/native_mesh/component_a/a.cpp new file mode 100644 index 000000000..2698cd8d9 --- /dev/null +++ b/crates/cpp/tests/native_mesh/component_a/a.cpp @@ -0,0 +1,66 @@ +// Generated by `wit-bindgen` 0.3.0. DO NOT EDIT! + +// Ensure that the *_component_type.o object is linked in +#ifdef __wasm32__ +extern void __component_type_object_force_link_a(void); +void __component_type_object_force_link_a_public_use_in_this_compilation_unit( + void) { + __component_type_object_force_link_a(); +} +#endif +#include "a_cpp.h" +#include // realloc + +extern "C" void *cabi_realloc(void *ptr, size_t old_size, size_t align, + size_t new_size); + +__attribute__((__weak__, __export_name__("cabi_realloc"))) void * +cabi_realloc(void *ptr, size_t old_size, size_t align, size_t new_size) { + (void)old_size; + if (new_size == 0) + return (void *)align; + void *ret = realloc(ptr, new_size); + if (!ret) + abort(); + return ret; +} + +extern "C" __attribute__((import_module("foo:foo/resources"))) +__attribute__((import_name("[resource-drop]r"))) void +fooX3AfooX2FresourcesX00X5Bresource_dropX5Dr(uint8_t *); +extern "C" __attribute__((import_module("foo:foo/resources"))) +__attribute__((import_name("[constructor]r"))) +uint8_t *fooX3AfooX2FresourcesX00X5BconstructorX5Dr(int32_t); +extern "C" __attribute__((import_module("foo:foo/resources"))) +__attribute__((import_name("[method]r.add"))) void +fooX3AfooX2FresourcesX00X5BmethodX5DrX2Eadd(uint8_t *, int32_t); +extern "C" __attribute__((import_module("foo:foo/resources"))) +__attribute__((import_name("create"))) uint8_t * +fooX3AfooX2FresourcesX00create(); +extern "C" __attribute__((import_module("foo:foo/resources"))) +__attribute__((import_name("consume"))) void +fooX3AfooX2FresourcesX00consume(uint8_t *); +foo::foo::resources::R::~R() { + if (handle != nullptr) { + fooX3AfooX2FresourcesX00X5Bresource_dropX5Dr(handle); + } +} +foo::foo::resources::R::R(uint32_t a) { + auto ret = fooX3AfooX2FresourcesX00X5BconstructorX5Dr((int32_t(a))); + this->handle = wit::ResourceImportBase{ret}.into_handle(); +} +void foo::foo::resources::R::Add(uint32_t b) const { + fooX3AfooX2FresourcesX00X5BmethodX5DrX2Eadd((*this).get_handle(), + (int32_t(b))); +} +foo::foo::resources::R::R(wit::ResourceImportBase &&b) + : wit::ResourceImportBase(std::move(b)) {} +foo::foo::resources::R foo::foo::resources::Create() { + auto ret = fooX3AfooX2FresourcesX00create(); + return wit::ResourceImportBase{ret}; +} +void foo::foo::resources::Consume(R &&o) { + fooX3AfooX2FresourcesX00consume(o.into_handle()); +} + +// Component Adapters diff --git a/crates/cpp/tests/native_mesh/component_a/a_cpp.h b/crates/cpp/tests/native_mesh/component_a/a_cpp.h new file mode 100644 index 000000000..01899e67e --- /dev/null +++ b/crates/cpp/tests/native_mesh/component_a/a_cpp.h @@ -0,0 +1,29 @@ +// Generated by `wit-bindgen` 0.3.0. DO NOT EDIT! +#ifndef __CPP_GUEST_BINDINGS_A_H +#define __CPP_GUEST_BINDINGS_A_H +#define WIT_SYMMETRIC +#include +#include +#include +#include +namespace foo { +namespace foo { +namespace resources { +class R : public wit::ResourceImportBase { + +public: + ~R(); + R(uint32_t a); + void Add(uint32_t b) const; + R(wit::ResourceImportBase &&); + R(R &&) = default; + R &operator=(R &&) = default; +}; + +R Create(); +void Consume(R &&o); +} // namespace resources +} // namespace foo +} // namespace foo + +#endif diff --git a/crates/cpp/tests/native_mesh/component_a/libcomponent_b.so b/crates/cpp/tests/native_mesh/component_a/libcomponent_b.so new file mode 120000 index 000000000..4c8d224e8 --- /dev/null +++ b/crates/cpp/tests/native_mesh/component_a/libcomponent_b.so @@ -0,0 +1 @@ +../component_b/libcomponent_b.so \ No newline at end of file diff --git a/crates/cpp/tests/native_mesh/component_a/libmesh.so b/crates/cpp/tests/native_mesh/component_a/libmesh.so new file mode 120000 index 000000000..30d34c7ac --- /dev/null +++ b/crates/cpp/tests/native_mesh/component_a/libmesh.so @@ -0,0 +1 @@ +../mesh/libmesh.so \ No newline at end of file diff --git a/crates/cpp/tests/native_mesh/component_a/main.cpp b/crates/cpp/tests/native_mesh/component_a/main.cpp new file mode 100644 index 000000000..51761d66e --- /dev/null +++ b/crates/cpp/tests/native_mesh/component_a/main.cpp @@ -0,0 +1,12 @@ + +#include "a_cpp.h" + +int main() { + { + auto obj = foo::foo::resources::R(5); + obj.Add(2); + } + auto obj2 = foo::foo::resources::Create(); + foo::foo::resources::Consume(std::move(obj2)); + return 0; +} diff --git a/crates/cpp/tests/native_mesh/component_b/.gitignore b/crates/cpp/tests/native_mesh/component_b/.gitignore new file mode 100644 index 000000000..eee26b6b8 --- /dev/null +++ b/crates/cpp/tests/native_mesh/component_b/.gitignore @@ -0,0 +1 @@ +/libcomponent_b.so diff --git a/crates/cpp/tests/native_mesh/component_b/Makefile b/crates/cpp/tests/native_mesh/component_b/Makefile new file mode 100644 index 000000000..2fff54abd --- /dev/null +++ b/crates/cpp/tests/native_mesh/component_b/Makefile @@ -0,0 +1,12 @@ +CXXFLAGS=-fPIC -g -O0 -I../../../helper-types + +all: libcomponent_b.so + +libcomponent_b.so: b.cpp impl.cpp + $(CXX) -shared $(CXXFLAGS) -o $@ $^ + +bindgen: + ../../../../../target/debug/wit-bindgen cpp ../wit -w b --wasm64 --format + +clean: + -rm *~ *.so *.o diff --git a/crates/cpp/tests/native_mesh/component_b/b.cpp b/crates/cpp/tests/native_mesh/component_b/b.cpp new file mode 100644 index 000000000..7627a7cc2 --- /dev/null +++ b/crates/cpp/tests/native_mesh/component_b/b.cpp @@ -0,0 +1,81 @@ +// Generated by `wit-bindgen` 0.3.0. DO NOT EDIT! + +// Ensure that the *_component_type.o object is linked in +#ifdef __wasm32__ +extern void __component_type_object_force_link_b(void); +void __component_type_object_force_link_b_public_use_in_this_compilation_unit( + void) { + __component_type_object_force_link_b(); +} +#endif +#include "b_cpp.h" +#include // realloc + +extern "C" void *cabi_realloc(void *ptr, size_t old_size, size_t align, + size_t new_size); + +__attribute__((__weak__, __export_name__("cabi_realloc"))) void * +cabi_realloc(void *ptr, size_t old_size, size_t align, size_t new_size) { + (void)old_size; + if (new_size == 0) + return (void *)align; + void *ret = realloc(ptr, new_size); + if (!ret) + abort(); + return ret; +} + +extern "C" __attribute__((import_module("[export]foo:foo/resources"))) +__attribute__((import_name("[resource-new]r"))) int32_t +X5BexportX5DfooX3AfooX2FresourcesX00X5Bresource_newX5Dr(uint8_t *); +extern "C" __attribute__((import_module("[export]foo:foo/resources"))) +__attribute__((import_name("[resource-rep]r"))) +uint8_t *X5BexportX5DfooX3AfooX2FresourcesX00X5Bresource_repX5Dr(int32_t); +extern "C" __attribute__((import_module("[export]foo:foo/resources"))) +__attribute__((import_name("[resource-drop]r"))) void + X5BexportX5DfooX3AfooX2FresourcesX00X5Bresource_dropX5Dr(int32_t); +extern "C" __attribute__((__export_name__("foo:foo/resources#[dtor]r"))) void +fooX3AfooX2FresourcesX23X5BdtorX5Dr(uint8_t *arg0) { + ((exports::foo::foo::resources::R *)arg0)->handle = -1; + exports::foo::foo::resources::R::Dtor( + (exports::foo::foo::resources::R *)arg0); +} +extern "C" + __attribute__((__export_name__("foo:foo/resources#[constructor]r"))) int32_t + fooX3AfooX2FresourcesX23X5BconstructorX5Dr(int32_t arg0) { + auto result0 = exports::foo::foo::resources::R::New((uint32_t(arg0))); + return result0.release()->handle; +} +extern "C" + __attribute__((__export_name__("foo:foo/resources#[method]r.add"))) void + fooX3AfooX2FresourcesX23X5BmethodX5DrX2Eadd(uint8_t *arg0, int32_t arg1) { + (std::ref(*(exports::foo::foo::resources::R *)arg0)) + .get() + .Add((uint32_t(arg1))); +} +int32_t exports::foo::foo::resources::R::ResourceNew(R *self) { + return X5BexportX5DfooX3AfooX2FresourcesX00X5Bresource_newX5Dr( + (uint8_t *)self); +} +exports::foo::foo::resources::R * +exports::foo::foo::resources::R::ResourceRep(int32_t id) { + return (exports::foo::foo::resources::R *) + X5BexportX5DfooX3AfooX2FresourcesX00X5Bresource_repX5Dr(id); +} +void exports::foo::foo::resources::R::ResourceDrop(int32_t id) { + X5BexportX5DfooX3AfooX2FresourcesX00X5Bresource_dropX5Dr(id); +} +extern "C" __attribute__((__export_name__("foo:foo/resources#create"))) int32_t +fooX3AfooX2FresourcesX23create() { + auto result0 = exports::foo::foo::resources::Create(); + return result0.release()->handle; +} +extern "C" __attribute__((__export_name__("foo:foo/resources#consume"))) void +fooX3AfooX2FresourcesX23consume(int32_t arg0) { + auto obj0 = exports::foo::foo::resources::R::Owned( + exports::foo::foo::resources::R::ResourceRep(arg0)); + //obj0->into_handle(); + exports::foo::foo::resources::Consume(std::move(obj0)); +} + +// Component Adapters diff --git a/crates/cpp/tests/native_mesh/component_b/b_cpp.h b/crates/cpp/tests/native_mesh/component_b/b_cpp.h new file mode 100644 index 000000000..8146a7098 --- /dev/null +++ b/crates/cpp/tests/native_mesh/component_b/b_cpp.h @@ -0,0 +1,19 @@ +// Generated by `wit-bindgen` 0.3.0. DO NOT EDIT! +#ifndef __CPP_GUEST_BINDINGS_B_H +#define __CPP_GUEST_BINDINGS_B_H +#include "exports-foo-foo-resources-R.h" +#include +#include +// export_interface Interface(Id { idx: 0 }) +namespace exports { +namespace foo { +namespace foo { +namespace resources { +R::Owned Create(); +void Consume(R::Owned o); +} // namespace resources +} // namespace foo +} // namespace foo +} // namespace exports + +#endif diff --git a/crates/cpp/tests/native_mesh/component_b/exports-foo-foo-resources-R.h b/crates/cpp/tests/native_mesh/component_b/exports-foo-foo-resources-R.h new file mode 100644 index 000000000..b2c0ab0d6 --- /dev/null +++ b/crates/cpp/tests/native_mesh/component_b/exports-foo-foo-resources-R.h @@ -0,0 +1,31 @@ +#pragma once +#include +#include +#include +#include +/* User class definition file, autogenerated once, then user modified + * Updated versions of this file are generated into R.template. + */ +namespace exports { +namespace foo { +namespace foo { +namespace resources { +class R : public wit::ResourceExportBase { + uint32_t value; + +public: + static void Dtor(R *self) { delete self; } + R(uint32_t a) : value(a) {} + static Owned New(uint32_t a) { return Owned(new R(a)); } + void Add(uint32_t b) { value += b; } + static int32_t ResourceNew(R *self); + static R *ResourceRep(int32_t id); + static void ResourceDrop(int32_t id); + + uint32_t get_value() const { return value; } +}; + +} // namespace resources +} // namespace foo +} // namespace foo +} // namespace exports diff --git a/crates/cpp/tests/native_mesh/component_b/impl.cpp b/crates/cpp/tests/native_mesh/component_b/impl.cpp new file mode 100644 index 000000000..b16cdccb6 --- /dev/null +++ b/crates/cpp/tests/native_mesh/component_b/impl.cpp @@ -0,0 +1,8 @@ +#include "b_cpp.h" + +exports::foo::foo::resources::R::Owned exports::foo::foo::resources::Create() { + return R::New(17); +} +void exports::foo::foo::resources::Consume(R::Owned o) { + printf("Consumed with %d\n", o->get_value()); +} diff --git a/crates/cpp/tests/native_mesh/mesh/Makefile b/crates/cpp/tests/native_mesh/mesh/Makefile new file mode 100644 index 000000000..523755e76 --- /dev/null +++ b/crates/cpp/tests/native_mesh/mesh/Makefile @@ -0,0 +1,12 @@ +CXXFLAGS=-fPIC -g -O0 -I../../../helper-types + +all: libmesh.so + +libmesh.so: mesh_native.cpp impl.cpp + $(CXX) -shared $(CXXFLAGS) -o $@ $^ libcomponent_b.so + +bindgen: + ../../../../../target/debug/wit-bindgen cpp ../wit -w mesh --wasm64 --format --direct --split-interfaces --internal-prefix mesh + +clean: + -rm *~ libmesh.so *.o diff --git a/crates/cpp/tests/native_mesh/mesh/impl.cpp b/crates/cpp/tests/native_mesh/mesh/impl.cpp new file mode 100644 index 000000000..f449e4bc0 --- /dev/null +++ b/crates/cpp/tests/native_mesh/mesh/impl.cpp @@ -0,0 +1,22 @@ + +#include "mesh_cpp_native.h" + +mesh::foo::foo::resources::R::R(uint32_t a) +: impl(exports::foo::foo::resources::R(a)) {} + +mesh::foo::foo::resources::R::R(exports::foo::foo::resources::R && a) +: impl(std::move(a)) {} + +void mesh::foo::foo::resources::R::Add(uint32_t b) { + impl.Add(b); +} + +mesh::foo::foo::resources::R::Owned +mesh::foo::foo::resources::Create() { + return mesh::foo::foo::resources::R::Owned(new mesh::foo::foo::resources::R + (exports::foo::foo::resources::Create())); +} + +void mesh::foo::foo::resources::Consume(mesh::foo::foo::resources::R::Owned obj) { + exports::foo::foo::resources::Consume(obj->into_inner()); +} diff --git a/crates/cpp/tests/native_mesh/mesh/libcomponent_b.so b/crates/cpp/tests/native_mesh/mesh/libcomponent_b.so new file mode 120000 index 000000000..4c8d224e8 --- /dev/null +++ b/crates/cpp/tests/native_mesh/mesh/libcomponent_b.so @@ -0,0 +1 @@ +../component_b/libcomponent_b.so \ No newline at end of file diff --git a/crates/cpp/tests/native_mesh/mesh/mesh-exports-foo-foo-resources-R.h b/crates/cpp/tests/native_mesh/mesh/mesh-exports-foo-foo-resources-R.h new file mode 100644 index 000000000..790301f35 --- /dev/null +++ b/crates/cpp/tests/native_mesh/mesh/mesh-exports-foo-foo-resources-R.h @@ -0,0 +1,26 @@ +#pragma once +#include +#include +#include +#include +namespace mesh { +namespace exports { +namespace foo { +namespace foo { +namespace resources { +class R : public wit::ResourceExportBase { + +public: + ~R(); + R(uint32_t a); + void Add(uint32_t b) const; + R(wit::ResourceExportBase &&); + R(R &&) = default; + R &operator=(R &&) = default; +}; + +} // namespace resources +} // namespace foo +} // namespace foo +} // namespace exports +} // namespace mesh diff --git a/crates/cpp/tests/native_mesh/mesh/mesh-exports-foo-foo-resources.h b/crates/cpp/tests/native_mesh/mesh/mesh-exports-foo-foo-resources.h new file mode 100644 index 000000000..4e4bc5e58 --- /dev/null +++ b/crates/cpp/tests/native_mesh/mesh/mesh-exports-foo-foo-resources.h @@ -0,0 +1,17 @@ +#pragma once +#include "mesh-exports-foo-foo-resources-R.h" +#include +#include +// export_interface Interface(Id { idx: 0 }) +namespace mesh { +namespace exports { +namespace foo { +namespace foo { +namespace resources { +R Create(); +void Consume(R &&o); +} // namespace resources +} // namespace foo +} // namespace foo +} // namespace exports +} // namespace mesh diff --git a/crates/cpp/tests/native_mesh/mesh/mesh_cpp_native.h b/crates/cpp/tests/native_mesh/mesh/mesh_cpp_native.h new file mode 100644 index 000000000..be64eda70 --- /dev/null +++ b/crates/cpp/tests/native_mesh/mesh/mesh_cpp_native.h @@ -0,0 +1,37 @@ +// Generated by `wit-bindgen` 0.3.0. DO NOT EDIT! +#ifndef __CPP_NATIVE_BINDINGS_MESH_H +#define __CPP_NATIVE_BINDINGS_MESH_H +#define WIT_HOST_DIRECT +#include "mesh-exports-foo-foo-resources.h" +#include +#include +#include +#include +/* User class definition file, autogenerated once, then user modified + * Updated versions of this file are generated into R.template. + */ +namespace mesh { +namespace foo { +namespace foo { +namespace resources { +class R : public wit::ResourceImportBase { + exports::foo::foo::resources::R impl; +public: + static void Dtor(R *self) { delete self; } + R(uint32_t a); + R(exports::foo::foo::resources::R && a); + static Owned New(uint32_t a) { return Owned(new R(a)); } + void Add(uint32_t b); + exports::foo::foo::resources::R into_inner() { + return std::move(impl); + } +}; + +R::Owned Create(); +void Consume(R::Owned o); +} // namespace resources +} // namespace foo +} // namespace foo +} // namespace mesh + +#endif diff --git a/crates/cpp/tests/native_mesh/mesh/mesh_native.cpp b/crates/cpp/tests/native_mesh/mesh/mesh_native.cpp new file mode 100644 index 000000000..e399d8cd7 --- /dev/null +++ b/crates/cpp/tests/native_mesh/mesh/mesh_native.cpp @@ -0,0 +1,72 @@ +// Generated by `wit-bindgen` 0.3.0. DO NOT EDIT! +#include "mesh_cpp_native.h" +template std::map wit::ResourceTable::resources; +#include +extern "C" void fooX3AfooX2FresourcesX23X5BdtorX5Dr(uint8_t *); +extern "C" int32_t fooX3AfooX2FresourcesX23X5BconstructorX5Dr(int32_t); +extern "C" void fooX3AfooX2FresourcesX23X5BmethodX5DrX2Eadd(uint8_t *, int32_t); +extern "C" int32_t fooX3AfooX2FresourcesX23create(); +extern "C" void fooX3AfooX2FresourcesX23consume(int32_t); +extern "C" void fooX3AfooX2FresourcesX00X5Bresource_dropX5Dr(int32_t arg0) { + auto ptr = mesh::foo::foo::resources::R::remove_resource(arg0); + assert(ptr.has_value()); + mesh::foo::foo::resources::R::Dtor(*ptr); +} +extern "C" int32_t fooX3AfooX2FresourcesX00X5BconstructorX5Dr(int32_t arg0) { + auto result0 = mesh::foo::foo::resources::R::New((uint32_t(arg0))); + return result0.release()->get_handle(); +} +extern "C" void fooX3AfooX2FresourcesX00X5BmethodX5DrX2Eadd(int32_t arg0, + int32_t arg1) { + (**mesh::foo::foo::resources::R::lookup_resource(arg0)).Add((uint32_t(arg1))); +} +extern "C" int32_t fooX3AfooX2FresourcesX00create() { + auto result0 = mesh::foo::foo::resources::Create(); + return result0.release()->get_handle(); +} +extern "C" void fooX3AfooX2FresourcesX00consume(int32_t arg0) { + auto obj0 = mesh::foo::foo::resources::R::remove_resource(arg0); + assert(obj0.has_value()); + mesh::foo::foo::resources::Consume( + mesh::foo::foo::resources::R::Owned(*obj0)); +} +mesh::exports::foo::foo::resources::R::~R() { + if (this->rep) { + fooX3AfooX2FresourcesX23X5BdtorX5Dr(this->rep); + } +} +mesh::exports::foo::foo::resources::R::R(uint32_t a) { + auto ret = fooX3AfooX2FresourcesX23X5BconstructorX5Dr((int32_t(a))); + wit::ResourceExportBase retobj = wit::ResourceExportBase{ret}; + this->index = retobj.get_handle(); + this->rep = retobj.take_rep(); +} +void mesh::exports::foo::foo::resources::R::Add(uint32_t b) const { + fooX3AfooX2FresourcesX23X5BmethodX5DrX2Eadd((*this).get_rep(), (int32_t(b))); +} +mesh::exports::foo::foo::resources::R::R(wit::ResourceExportBase &&b) + : wit::ResourceExportBase(std::move(b)) {} +extern "C" int32_t +X5BexportX5DfooX3AfooX2FresourcesX00X5Bresource_newX5Dr(uint8_t *arg0) { + return mesh::exports::foo::foo::resources::R::store_resource(std::move(arg0)); +} +extern "C" uint8_t * +X5BexportX5DfooX3AfooX2FresourcesX00X5Bresource_repX5Dr(int32_t arg0) { + return *mesh::exports::foo::foo::resources::R::lookup_resource(arg0); +} +extern "C" void +X5BexportX5DfooX3AfooX2FresourcesX00X5Bresource_dropX5Dr(int32_t arg0) { + auto obj = mesh::exports::foo::foo::resources::R::remove_resource(arg0); + fooX3AfooX2FresourcesX23X5BdtorX5Dr(*obj); +} +mesh::exports::foo::foo::resources::R +mesh::exports::foo::foo::resources::Create() { + auto ret = fooX3AfooX2FresourcesX23create(); + return wit::ResourceExportBase{ret}; +} +void mesh::exports::foo::foo::resources::Consume(R &&o) { + auto rep0 = o.take_rep(); + fooX3AfooX2FresourcesX23consume(o.get_handle()); +} + +// Component Adapters diff --git a/crates/cpp/tests/native_mesh/wit/resources_simple.wit b/crates/cpp/tests/native_mesh/wit/resources_simple.wit new file mode 100644 index 000000000..79ff53ce1 --- /dev/null +++ b/crates/cpp/tests/native_mesh/wit/resources_simple.wit @@ -0,0 +1,22 @@ +package foo:foo; + +interface resources { + resource r { + constructor(a: u32); + add: func(b: u32); + } + create: func() -> r; + //borrows: func(o: borrow); + consume: func(o: r); +} + +world a { + import resources; +} +world b { + export resources; +} +world mesh { + import resources; + export resources; +} diff --git a/crates/cpp/tests/native_resources/.gitignore b/crates/cpp/tests/native_resources/.gitignore new file mode 100644 index 000000000..4fc762d50 --- /dev/null +++ b/crates/cpp/tests/native_resources/.gitignore @@ -0,0 +1,4 @@ +/*.o +/*.so +/app-resources +/*.template diff --git a/crates/cpp/tests/native_resources/Makefile b/crates/cpp/tests/native_resources/Makefile new file mode 100644 index 000000000..32746aaa4 --- /dev/null +++ b/crates/cpp/tests/native_resources/Makefile @@ -0,0 +1,21 @@ +CXXFLAGS=-g -O0 -I../../helper-types +WIT_BINDGEN=../../../../target/debug/wit-bindgen + +all: libresources.so app-resources + +app-resources: the_world_native.o main.o + $(CXX) $(CXXFLAGS) -o $@ $^ -L. -lresources + +bindgen: wit/resources_simple.wit + cd guest; ../$(WIT_BINDGEN) cpp ../wit --wasm64 --format + $(WIT_BINDGEN) cpp wit --wasm64 --format --direct + cd rust/src ; ../../$(WIT_BINDGEN) rust ../../wit --wasm64 + +clean: + -rm *.o app-resources + +run: app-resources + LD_LIBRARY_PATH=. ./app-resources + +valgrind: app-resources + LD_LIBRARY_PATH=. valgrind --leak-check=full ./app-resources diff --git a/crates/cpp/tests/native_resources/foo-foo-resources-R.h b/crates/cpp/tests/native_resources/foo-foo-resources-R.h new file mode 100644 index 000000000..e3591eafe --- /dev/null +++ b/crates/cpp/tests/native_resources/foo-foo-resources-R.h @@ -0,0 +1,22 @@ +/* User class definition file, autogenerated once, then user modified + * Updated versions of this file are generated into + * foo-foo-resources-R.h.template. + */ +namespace foo { +namespace foo { +namespace resources { +class R : public wit::ResourceImportBase { + uint32_t value; + +public: + static void Dtor(R *self) { delete self; }; + R(uint32_t a) : value(a) {} + static Owned New(uint32_t a) { return Owned(new R(a)); } + void Add(uint32_t b) { value += b; } + + uint32_t GetValue() const { return value; } +}; + +} // namespace resources +} // namespace foo +} // namespace foo diff --git a/crates/cpp/tests/native_resources/guest/.gitignore b/crates/cpp/tests/native_resources/guest/.gitignore new file mode 100644 index 000000000..d6ec319d2 --- /dev/null +++ b/crates/cpp/tests/native_resources/guest/.gitignore @@ -0,0 +1,3 @@ +/*.o +/*.so +/*.template diff --git a/crates/cpp/tests/native_resources/guest/Makefile b/crates/cpp/tests/native_resources/guest/Makefile new file mode 100644 index 000000000..6522a3264 --- /dev/null +++ b/crates/cpp/tests/native_resources/guest/Makefile @@ -0,0 +1,15 @@ +CXXFLAGS=-g -O0 -I../../../helper-types + +all: libresources.so + +libresources.so: the_world.pie.o guest.pie.o + $(CXX) $(CXXFLAGS) -shared -o $@ $^ -Wl,--version-script=guest.verscr + +%.pie.o: %.cpp + $(CXX) $(CXXFLAGS) -fPIE -o $@ -c $^ + +guest.wasm: the_world.cpp guest.cpp + /opt/wasi-sdk/bin/clang++ -o $@ $^ $(CXXFLAGS) + +clean: + -rm *.o libresources.so diff --git a/crates/cpp/tests/native_resources/guest/exports-foo-foo-resources-R.h b/crates/cpp/tests/native_resources/guest/exports-foo-foo-resources-R.h new file mode 100644 index 000000000..ce8d45fd0 --- /dev/null +++ b/crates/cpp/tests/native_resources/guest/exports-foo-foo-resources-R.h @@ -0,0 +1,28 @@ +/* User class definition file, autogenerated once, then user modified + * Updated versions of this file are generated into + * exports-foo-foo-resources-R.h.template. + */ +#include +namespace exports { +namespace foo { +namespace foo { +namespace resources { +class R : public wit::ResourceExportBase { + uint32_t value; + +public: + static void Dtor(R *self) { delete self; }; + R(uint32_t a) : value(a) {} + static Owned New(uint32_t a) { return Owned(new R(a)); } + void Add(uint32_t b) { value += b; } + static int32_t ResourceNew(R *self); + static R* ResourceRep(int32_t id); + static void ResourceDrop(int32_t id); + + uint32_t GetValue() const { return value; } +}; + +} // namespace resources +} // namespace foo +} // namespace foo +} // namespace exports diff --git a/crates/cpp/tests/native_resources/guest/guest.cpp b/crates/cpp/tests/native_resources/guest/guest.cpp new file mode 100644 index 000000000..1cb4096cc --- /dev/null +++ b/crates/cpp/tests/native_resources/guest/guest.cpp @@ -0,0 +1,22 @@ +#include "the_world_cpp.h" +#include + +exports::foo::foo::resources::R::Owned exports::foo::foo::resources::Create() { + return R::Owned(new R(1)); +} + +void exports::foo::foo::resources::Borrows(std::reference_wrapper o) { + printf("resource borrowed with %d\n", o.get().GetValue()); +} + +void exports::foo::foo::resources::Consume(R::Owned o) { + printf("resource consumed with %d\n", o->GetValue()); + o.reset(); + + printf("exercise the other direction\n"); + auto obj = ::foo::foo::resources::Create(); + obj.Add(12); + ::foo::foo::resources::Borrows(obj); + ::foo::foo::resources::Consume(std::move(obj)); + auto obj2 = ::foo::foo::resources::R{42}; +} diff --git a/crates/cpp/tests/native_resources/guest/guest.verscr b/crates/cpp/tests/native_resources/guest/guest.verscr new file mode 100644 index 000000000..e6862b74e --- /dev/null +++ b/crates/cpp/tests/native_resources/guest/guest.verscr @@ -0,0 +1,5 @@ +{ + global: + fooX3AfooX2FresourcesX23*; + local: *; +}; diff --git a/crates/cpp/tests/native_resources/guest/the_world.cpp b/crates/cpp/tests/native_resources/guest/the_world.cpp new file mode 100644 index 000000000..d57fdd97b --- /dev/null +++ b/crates/cpp/tests/native_resources/guest/the_world.cpp @@ -0,0 +1,129 @@ +// Generated by `wit-bindgen` 0.3.0. DO NOT EDIT! + +// Ensure that the *_component_type.o object is linked in +#ifdef __wasm32__ +extern void __component_type_object_force_link_the_world(void); +void __component_type_object_force_link_the_world_public_use_in_this_compilation_unit( + void) { + __component_type_object_force_link_the_world(); +} +#endif +#include "the_world_cpp.h" +#include // realloc + +extern "C" void *cabi_realloc(void *ptr, size_t old_size, size_t align, + size_t new_size); + +__attribute__((__weak__, __export_name__("cabi_realloc"))) void * +cabi_realloc(void *ptr, size_t old_size, size_t align, size_t new_size) { + (void)old_size; + if (new_size == 0) + return (void *)align; + void *ret = realloc(ptr, new_size); + if (!ret) + abort(); + return ret; +} + +extern "C" __attribute__((import_module("foo:foo/resources"))) +__attribute__((import_name("[resource-drop]r"))) void + fooX3AfooX2FresourcesX00X5Bresource_dropX5Dr(int32_t); +extern "C" __attribute__((import_module("foo:foo/resources"))) +__attribute__((import_name("[constructor]r"))) +int32_t fooX3AfooX2FresourcesX00X5BconstructorX5Dr(int32_t); +extern "C" __attribute__((import_module("foo:foo/resources"))) +__attribute__((import_name("[method]r.add"))) void + fooX3AfooX2FresourcesX00X5BmethodX5DrX2Eadd(int32_t, int32_t); +extern "C" __attribute__((import_module("foo:foo/resources"))) +__attribute__((import_name("create"))) int32_t +fooX3AfooX2FresourcesX00create(); +extern "C" __attribute__((import_module("foo:foo/resources"))) +__attribute__((import_name("borrows"))) void + fooX3AfooX2FresourcesX00borrows(int32_t); +extern "C" __attribute__((import_module("foo:foo/resources"))) +__attribute__((import_name("consume"))) void + fooX3AfooX2FresourcesX00consume(int32_t); +extern "C" __attribute__((import_module("[export]foo:foo/resources"))) +__attribute__((import_name("[resource-new]r"))) int32_t +X5BexportX5DfooX3AfooX2FresourcesX00X5Bresource_newX5Dr(uint8_t *); +extern "C" __attribute__((import_module("[export]foo:foo/resources"))) +__attribute__((import_name("[resource-rep]r"))) +uint8_t *X5BexportX5DfooX3AfooX2FresourcesX00X5Bresource_repX5Dr(int32_t); +extern "C" __attribute__((import_module("[export]foo:foo/resources"))) +__attribute__((import_name("[resource-drop]r"))) void + X5BexportX5DfooX3AfooX2FresourcesX00X5Bresource_dropX5Dr(int32_t); +foo::foo::resources::R::~R() { + if (handle >= 0) { + fooX3AfooX2FresourcesX00X5Bresource_dropX5Dr(handle); + } +} +foo::foo::resources::R::R(uint32_t a) { + auto ret = fooX3AfooX2FresourcesX00X5BconstructorX5Dr((int32_t(a))); + this->handle = wit::ResourceImportBase{ret}.into_handle(); +} +void foo::foo::resources::R::Add(uint32_t b) const { + fooX3AfooX2FresourcesX00X5BmethodX5DrX2Eadd((*this).get_handle(), + (int32_t(b))); +} +foo::foo::resources::R::R(wit::ResourceImportBase &&b) + : wit::ResourceImportBase(std::move(b)) {} +foo::foo::resources::R foo::foo::resources::Create() { + auto ret = fooX3AfooX2FresourcesX00create(); + return wit::ResourceImportBase{ret}; +} +void foo::foo::resources::Borrows(std::reference_wrapper o) { + fooX3AfooX2FresourcesX00borrows(o.get().get_handle()); +} +void foo::foo::resources::Consume(R &&o) { + fooX3AfooX2FresourcesX00consume(o.into_handle()); +} +extern "C" __attribute__((__export_name__("foo:foo/resources#[dtor]r"))) void +fooX3AfooX2FresourcesX23X5BdtorX5Dr(uint8_t *arg0) { + ((exports::foo::foo::resources::R *)arg0)->handle = -1; + exports::foo::foo::resources::R::Dtor( + (exports::foo::foo::resources::R *)arg0); +} +extern "C" + __attribute__((__export_name__("foo:foo/resources#[constructor]r"))) int32_t + fooX3AfooX2FresourcesX23X5BconstructorX5Dr(int32_t arg0) { + auto result0 = exports::foo::foo::resources::R::New((uint32_t(arg0))); + return result0.release()->handle; +} +extern "C" + __attribute__((__export_name__("foo:foo/resources#[method]r.add"))) void + fooX3AfooX2FresourcesX23X5BmethodX5DrX2Eadd(uint8_t *arg0, int32_t arg1) { + (std::ref(*(exports::foo::foo::resources::R *)arg0)) + .get() + .Add((uint32_t(arg1))); +} +int32_t exports::foo::foo::resources::R::ResourceNew(R *self) { + return X5BexportX5DfooX3AfooX2FresourcesX00X5Bresource_newX5Dr( + (uint8_t *)self); +} +exports::foo::foo::resources::R * +exports::foo::foo::resources::R::ResourceRep(int32_t id) { + return (exports::foo::foo::resources::R *) + X5BexportX5DfooX3AfooX2FresourcesX00X5Bresource_repX5Dr(id); +} +void exports::foo::foo::resources::R::ResourceDrop(int32_t id) { + X5BexportX5DfooX3AfooX2FresourcesX00X5Bresource_dropX5Dr(id); +} +extern "C" __attribute__((__export_name__("foo:foo/resources#create"))) int32_t +fooX3AfooX2FresourcesX23create() { + auto result0 = exports::foo::foo::resources::Create(); + return result0.release()->handle; +} +extern "C" __attribute__((__export_name__("foo:foo/resources#borrows"))) void +fooX3AfooX2FresourcesX23borrows(uint8_t* arg0) { + exports::foo::foo::resources::Borrows( + std::ref(*(exports::foo::foo::resources::R *)arg0)); +} +extern "C" __attribute__((__export_name__("foo:foo/resources#consume"))) void +fooX3AfooX2FresourcesX23consume(int32_t arg0) { + auto obj0 = exports::foo::foo::resources::R::Owned( + exports::foo::foo::resources::R::ResourceRep(arg0)); +// obj0->into_handle(); + exports::foo::foo::resources::Consume(std::move(obj0)); +} + +// Component Adapters diff --git a/crates/cpp/tests/native_resources/guest/the_world_cpp.h b/crates/cpp/tests/native_resources/guest/the_world_cpp.h new file mode 100644 index 000000000..d7afa91a2 --- /dev/null +++ b/crates/cpp/tests/native_resources/guest/the_world_cpp.h @@ -0,0 +1,43 @@ +// Generated by `wit-bindgen` 0.3.0. DO NOT EDIT! +#ifndef __CPP_GUEST_BINDINGS_THE_WORLD_H +#define __CPP_GUEST_BINDINGS_THE_WORLD_H +#include +#include +#include +#include +#include +namespace foo { +namespace foo { +namespace resources { +class R : public wit::ResourceImportBase { + +public: + ~R(); + R(uint32_t a); + void Add(uint32_t b) const; + R(wit::ResourceImportBase &&); + R(R &&) = default; + R &operator=(R &&) = default; +}; + +R Create(); +void Borrows(std::reference_wrapper o); +void Consume(R &&o); +// export_interface Interface(Id { idx: 0 }) +} // namespace resources +} // namespace foo +} // namespace foo +#include "exports-foo-foo-resources-R.h" +namespace exports { +namespace foo { +namespace foo { +namespace resources { +R::Owned Create(); +void Borrows(std::reference_wrapper o); +void Consume(R::Owned o); +} // namespace resources +} // namespace foo +} // namespace foo +} // namespace exports + +#endif diff --git a/crates/cpp/tests/native_resources/libresources.so b/crates/cpp/tests/native_resources/libresources.so new file mode 120000 index 000000000..8b53f48a4 --- /dev/null +++ b/crates/cpp/tests/native_resources/libresources.so @@ -0,0 +1 @@ +rust/target/debug/libresources.so \ No newline at end of file diff --git a/crates/cpp/tests/native_resources/main.cpp b/crates/cpp/tests/native_resources/main.cpp new file mode 100644 index 000000000..3ce2ea1b2 --- /dev/null +++ b/crates/cpp/tests/native_resources/main.cpp @@ -0,0 +1,22 @@ + +#include "the_world_cpp_native.h" +#include + +foo::foo::resources::R::Owned foo::foo::resources::Create() { + return R::New(1); +} +void foo::foo::resources::Borrows(std::reference_wrapper o) { + printf("resource borrowed with %d\n", o.get().GetValue()); +} +void foo::foo::resources::Consume(R::Owned o) { + printf("resource consumed with %d\n", o->GetValue()); +} + +int main() { + auto obj = exports::foo::foo::resources::Create(); + obj.Add(12); + exports::foo::foo::resources::Borrows(obj); + exports::foo::foo::resources::Consume(std::move(obj)); + auto obj2 = exports::foo::foo::resources::R{42}; + return 0; +} diff --git a/crates/cpp/tests/native_resources/rust/Cargo.lock b/crates/cpp/tests/native_resources/rust/Cargo.lock new file mode 100644 index 000000000..5e8e19daf --- /dev/null +++ b/crates/cpp/tests/native_resources/rust/Cargo.lock @@ -0,0 +1,308 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "indexmap" +version = "2.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "resources" +version = "0.1.0" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "spdx" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ef1a0fa1e39ac22972c8db23ff89aea700ab96aa87114e1fb55937a631a0c9" +dependencies = [ + "smallvec", +] + +[[package]] +name = "syn" +version = "2.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "wasm-encoder" +version = "0.202.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfd106365a7f5f7aa3c1916a98cbb3ad477f5ff96ddb130285a91c6e7429e67a" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasm-metadata" +version = "0.202.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "094aea3cb90e09f16ee25a4c0e324b3e8c934e7fd838bfa039aef5352f44a917" +dependencies = [ + "anyhow", + "indexmap", + "serde", + "serde_derive", + "serde_json", + "spdx", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.202.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6998515d3cf3f8b980ef7c11b29a9b1017d4cf86b99ae93b546992df9931413" +dependencies = [ + "bitflags", + "indexmap", + "semver", +] + +[[package]] +name = "wit-bindgen" +version = "0.24.0" +dependencies = [ + "wit-bindgen-rt", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.24.0" +dependencies = [ + "anyhow", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.24.0" +dependencies = [ + "bitflags", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.24.0" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.24.0" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.202.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c836b1fd9932de0431c1758d8be08212071b6bba0151f7bac826dbc4312a2a9" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.202.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744237b488352f4f27bca05a10acb79474415951c450e52ebd0da784c1df2bcc" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[patch.unused]] +name = "wit-parser" +version = "0.201.0" +source = "git+https://github.com/bytecodealliance/wasm-tools#9c01485c2713d926e8f5b67b3e6f62f792da8ba7" diff --git a/crates/cpp/tests/native_resources/rust/Cargo.toml b/crates/cpp/tests/native_resources/rust/Cargo.toml new file mode 100644 index 000000000..3117f59f0 --- /dev/null +++ b/crates/cpp/tests/native_resources/rust/Cargo.toml @@ -0,0 +1,16 @@ +[workspace] + +[package] +name = "resources" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +# wit-bindgen-rt = "0.21.0" +wit-bindgen = { path = "../../../../guest-rust" } + +[patch.crates-io] +wit-parser = { git = "https://github.com/bytecodealliance/wasm-tools" } diff --git a/crates/cpp/tests/native_resources/rust/generate.sh b/crates/cpp/tests/native_resources/rust/generate.sh new file mode 100755 index 000000000..03efe7aed --- /dev/null +++ b/crates/cpp/tests/native_resources/rust/generate.sh @@ -0,0 +1,4 @@ +#!/bin/sh +cargo component build +wasm-tools component new target/wasm32-wasi/debug/resources.wasm -o component.wasm --adapt ~/Downloads/wasi_snapshot_preview1.reactor\(1\).wasm +jco transpile component.wasm -o html --no-typescript --no-wasi-shim --map wasi:filesystem/*=./bytecodealliance/preview2-shim/filesystem.js --map wasi:cli/*=./bytecodealliance/preview2-shim/cli.js --map wasi:cli-base/*=./bytecodealliance/preview2-shim/cli.js --map wasi:io/*=./bytecodealliance/preview2-shim/io.js --map test:example/my-interface=./test_example/my-interface.js --map foo:foo/resources=./resources.js diff --git a/crates/cpp/tests/native_resources/rust/html/component.js b/crates/cpp/tests/native_resources/rust/html/component.js new file mode 100644 index 000000000..1dde25d99 --- /dev/null +++ b/crates/cpp/tests/native_resources/rust/html/component.js @@ -0,0 +1,1914 @@ +import { exit, getEnvironment, getStderr, getStdin, getStdout } from './bytecodealliance/preview2-shim/cli.js'; +import { Descriptor, filesystemErrorCode, getDirectories } from './bytecodealliance/preview2-shim/filesystem.js'; +import { Error as Error$1, InputStream, OutputStream } from './bytecodealliance/preview2-shim/io.js'; +import { R, borrows, consume, create } from './resources.js'; + +const base64Compile = str => WebAssembly.compile(typeof Buffer !== 'undefined' ? Buffer.from(str, 'base64') : Uint8Array.from(atob(str), b => b.charCodeAt(0))); + +let dv = new DataView(new ArrayBuffer()); +const dataView = mem => dv.buffer === mem.buffer ? dv : dv = new DataView(mem.buffer); + +const emptyFunc = () => {}; + +const isNode = typeof process !== 'undefined' && process.versions && process.versions.node; +let _fs; +async function fetchCompile (url) { + if (isNode) { + _fs = _fs || await import('fs/promises'); + return WebAssembly.compile(await _fs.readFile(url)); + } + return fetch(url).then(WebAssembly.compileStreaming); +} + +function getErrorPayload(e) { + if (e && hasOwnProperty.call(e, 'payload')) return e.payload; + return e; +} + +const handleTables = []; + +const hasOwnProperty = Object.prototype.hasOwnProperty; + +const instantiateCore = WebAssembly.instantiate; + +const T_FLAG = 1 << 30; + +function rscTableCreateOwn (table, rep) { + if (rep === 0) throw new Error('Invalid rep'); + const free = table[0] & ~T_FLAG; + if (free === 0) { + table.push(0); + table.push(rep | T_FLAG); + return (table.length >> 1) - 1; + } + table[0] = table[free << 1]; + table[free << 1] = 0; + table[(free << 1) + 1] = rep | T_FLAG; + return free; +} + +function rscTableRemove (table, handle) { + const scope = table[handle << 1]; + const val = table[(handle << 1) + 1]; + const own = (val & T_FLAG) !== 0; + const rep = val & ~T_FLAG; + if (val === 0 || (scope & T_FLAG) !== 0) throw new Error('Invalid handle'); + table[handle << 1] = table[0] | T_FLAG; + table[0] = handle | T_FLAG; + return { rep, scope, own }; +} + +const symbolCabiDispose = Symbol.for('cabiDispose'); + +const symbolRscHandle = Symbol('handle'); + +const symbolRscRep = Symbol.for('cabiRep'); + +const symbolDispose = Symbol.dispose || Symbol.for('dispose'); + +const toUint64 = val => BigInt.asUintN(64, BigInt(val)); + +function toUint32(val) { + return val >>> 0; +} + +const utf8Encoder = new TextEncoder(); + +let utf8EncodedLen = 0; +function utf8Encode(s, realloc, memory) { + if (typeof s !== 'string') throw new TypeError('expected a string'); + if (s.length === 0) { + utf8EncodedLen = 0; + return 1; + } + let allocLen = 0; + let ptr = 0; + let writtenTotal = 0; + while (s.length > 0) { + ptr = realloc(ptr, allocLen, 1, allocLen += s.length * 2); + const { read, written } = utf8Encoder.encodeInto( + s, + new Uint8Array(memory.buffer, ptr + writtenTotal, allocLen - writtenTotal), + ); + writtenTotal += written; + s = s.slice(read); + } + utf8EncodedLen = writtenTotal; + return ptr; +} + +let exports0; +const handleTable0 = [T_FLAG, 0]; +const captureTable0= new Map(); +let captureCnt0 = 0; +handleTables[0] = handleTable0; + +function trampoline2(arg0) { + const ret = new R(arg0 >>> 0); + if (!(ret instanceof R)) { + throw new Error('Resource error: Not a valid "R" resource.'); + } + var handle0 = ret[symbolRscHandle]; + + if (!handle0) { + const rep = ret[symbolRscRep] || ++captureCnt0; + captureTable0.set(rep, ret); + handle0 = rscTableCreateOwn(handleTable0, rep); + } + return handle0; +} + +function trampoline3(arg0, arg1) { + var handle1 = arg0; + var rep2 = handleTable0[(handle1 << 1) + 1] & ~T_FLAG; + var rsc0 = captureTable0.get(rep2); + if (!rsc0) { + rsc0 = Object.create(R.prototype); + Object.defineProperty(rsc0, symbolRscHandle, { writable: true, value: handle1}); + Object.defineProperty(rsc0, symbolRscRep, { writable: true, value: rep2}); + } + rsc0.add(arg1 >>> 0); + rsc0[symbolRscHandle] = null; +} + +function trampoline4() { + const ret = create(); + if (!(ret instanceof R)) { + throw new Error('Resource error: Not a valid "R" resource.'); + } + var handle0 = ret[symbolRscHandle]; + + if (!handle0) { + const rep = ret[symbolRscRep] || ++captureCnt0; + captureTable0.set(rep, ret); + handle0 = rscTableCreateOwn(handleTable0, rep); + } + return handle0; +} + +function trampoline5(arg0) { + var handle1 = arg0; + var rep2 = handleTable0[(handle1 << 1) + 1] & ~T_FLAG; + var rsc0 = captureTable0.get(rep2); + if (!rsc0) { + rsc0 = Object.create(R.prototype); + Object.defineProperty(rsc0, symbolRscHandle, { writable: true, value: handle1}); + Object.defineProperty(rsc0, symbolRscRep, { writable: true, value: rep2}); + } + borrows(rsc0); + rsc0[symbolRscHandle] = null; +} + +function trampoline6(arg0) { + var handle1 = arg0; + var rep2 = handleTable0[(handle1 << 1) + 1] & ~T_FLAG; + var rsc0 = captureTable0.get(rep2); + if (!rsc0) { + rsc0 = Object.create(R.prototype); + Object.defineProperty(rsc0, symbolRscHandle, { writable: true, value: handle1}); + Object.defineProperty(rsc0, symbolRscRep, { writable: true, value: rep2}); + } + else { + captureTable0.delete(rep2); + } + rscTableRemove(handleTable0, handle1); + consume(rsc0); +} +let exports1; +const handleTable2 = [T_FLAG, 0]; +const captureTable2= new Map(); +let captureCnt2 = 0; +handleTables[2] = handleTable2; + +function trampoline12() { + const ret = getStderr(); + if (!(ret instanceof OutputStream)) { + throw new Error('Resource error: Not a valid "OutputStream" resource.'); + } + var handle0 = ret[symbolRscHandle]; + + if (!handle0) { + const rep = ret[symbolRscRep] || ++captureCnt2; + captureTable2.set(rep, ret); + handle0 = rscTableCreateOwn(handleTable2, rep); + } + return handle0; +} + +function trampoline13(arg0) { + let variant0; + switch (arg0) { + case 0: { + variant0= { + tag: 'ok', + val: undefined + }; + break; + } + case 1: { + variant0= { + tag: 'err', + val: undefined + }; + break; + } + default: { + throw new TypeError('invalid variant discriminant for expected'); + } + } + exit(variant0); +} +const handleTable3 = [T_FLAG, 0]; +const captureTable3= new Map(); +let captureCnt3 = 0; +handleTables[3] = handleTable3; + +function trampoline14() { + const ret = getStdin(); + if (!(ret instanceof InputStream)) { + throw new Error('Resource error: Not a valid "InputStream" resource.'); + } + var handle0 = ret[symbolRscHandle]; + + if (!handle0) { + const rep = ret[symbolRscRep] || ++captureCnt3; + captureTable3.set(rep, ret); + handle0 = rscTableCreateOwn(handleTable3, rep); + } + return handle0; +} + +function trampoline15() { + const ret = getStdout(); + if (!(ret instanceof OutputStream)) { + throw new Error('Resource error: Not a valid "OutputStream" resource.'); + } + var handle0 = ret[symbolRscHandle]; + + if (!handle0) { + const rep = ret[symbolRscRep] || ++captureCnt2; + captureTable2.set(rep, ret); + handle0 = rscTableCreateOwn(handleTable2, rep); + } + return handle0; +} +let exports2; +let memory0; +let realloc0; +const handleTable4 = [T_FLAG, 0]; +const captureTable4= new Map(); +let captureCnt4 = 0; +handleTables[4] = handleTable4; + +function trampoline16(arg0) { + const ret = getDirectories(); + var vec3 = ret; + var len3 = vec3.length; + var result3 = realloc0(0, 0, 4, len3 * 12); + for (let i = 0; i < vec3.length; i++) { + const e = vec3[i]; + const base = result3 + i * 12;var [tuple0_0, tuple0_1] = e; + if (!(tuple0_0 instanceof Descriptor)) { + throw new Error('Resource error: Not a valid "Descriptor" resource.'); + } + var handle1 = tuple0_0[symbolRscHandle]; + + if (!handle1) { + const rep = tuple0_0[symbolRscRep] || ++captureCnt4; + captureTable4.set(rep, tuple0_0); + handle1 = rscTableCreateOwn(handleTable4, rep); + } + dataView(memory0).setInt32(base + 0, handle1, true); + var ptr2 = utf8Encode(tuple0_1, realloc0, memory0); + var len2 = utf8EncodedLen; + dataView(memory0).setInt32(base + 8, len2, true); + dataView(memory0).setInt32(base + 4, ptr2, true); + } + dataView(memory0).setInt32(arg0 + 4, len3, true); + dataView(memory0).setInt32(arg0 + 0, result3, true); +} + +function trampoline17(arg0, arg1, arg2) { + var handle1 = arg0; + var rep2 = handleTable4[(handle1 << 1) + 1] & ~T_FLAG; + var rsc0 = captureTable4.get(rep2); + if (!rsc0) { + rsc0 = Object.create(Descriptor.prototype); + Object.defineProperty(rsc0, symbolRscHandle, { writable: true, value: handle1}); + Object.defineProperty(rsc0, symbolRscRep, { writable: true, value: rep2}); + } + let ret; + try { + ret = { tag: 'ok', val: rsc0.writeViaStream(BigInt.asUintN(64, arg1))}; + } catch (e) { + ret = { tag: 'err', val: getErrorPayload(e) }; + } + rsc0[symbolRscHandle] = null; + var variant5 = ret; + switch (variant5.tag) { + case 'ok': { + const e = variant5.val; + dataView(memory0).setInt8(arg2 + 0, 0, true); + if (!(e instanceof OutputStream)) { + throw new Error('Resource error: Not a valid "OutputStream" resource.'); + } + var handle3 = e[symbolRscHandle]; + + if (!handle3) { + const rep = e[symbolRscRep] || ++captureCnt2; + captureTable2.set(rep, e); + handle3 = rscTableCreateOwn(handleTable2, rep); + } + dataView(memory0).setInt32(arg2 + 4, handle3, true); + break; + } + case 'err': { + const e = variant5.val; + dataView(memory0).setInt8(arg2 + 0, 1, true); + var val4 = e; + let enum4; + switch (val4) { + case 'access': { + enum4 = 0; + break; + } + case 'would-block': { + enum4 = 1; + break; + } + case 'already': { + enum4 = 2; + break; + } + case 'bad-descriptor': { + enum4 = 3; + break; + } + case 'busy': { + enum4 = 4; + break; + } + case 'deadlock': { + enum4 = 5; + break; + } + case 'quota': { + enum4 = 6; + break; + } + case 'exist': { + enum4 = 7; + break; + } + case 'file-too-large': { + enum4 = 8; + break; + } + case 'illegal-byte-sequence': { + enum4 = 9; + break; + } + case 'in-progress': { + enum4 = 10; + break; + } + case 'interrupted': { + enum4 = 11; + break; + } + case 'invalid': { + enum4 = 12; + break; + } + case 'io': { + enum4 = 13; + break; + } + case 'is-directory': { + enum4 = 14; + break; + } + case 'loop': { + enum4 = 15; + break; + } + case 'too-many-links': { + enum4 = 16; + break; + } + case 'message-size': { + enum4 = 17; + break; + } + case 'name-too-long': { + enum4 = 18; + break; + } + case 'no-device': { + enum4 = 19; + break; + } + case 'no-entry': { + enum4 = 20; + break; + } + case 'no-lock': { + enum4 = 21; + break; + } + case 'insufficient-memory': { + enum4 = 22; + break; + } + case 'insufficient-space': { + enum4 = 23; + break; + } + case 'not-directory': { + enum4 = 24; + break; + } + case 'not-empty': { + enum4 = 25; + break; + } + case 'not-recoverable': { + enum4 = 26; + break; + } + case 'unsupported': { + enum4 = 27; + break; + } + case 'no-tty': { + enum4 = 28; + break; + } + case 'no-such-device': { + enum4 = 29; + break; + } + case 'overflow': { + enum4 = 30; + break; + } + case 'not-permitted': { + enum4 = 31; + break; + } + case 'pipe': { + enum4 = 32; + break; + } + case 'read-only': { + enum4 = 33; + break; + } + case 'invalid-seek': { + enum4 = 34; + break; + } + case 'text-file-busy': { + enum4 = 35; + break; + } + case 'cross-device': { + enum4 = 36; + break; + } + default: { + if ((e) instanceof Error) { + console.error(e); + } + + throw new TypeError(`"${val4}" is not one of the cases of error-code`); + } + } + dataView(memory0).setInt8(arg2 + 4, enum4, true); + break; + } + default: { + throw new TypeError('invalid variant specified for result'); + } + } +} + +function trampoline18(arg0, arg1) { + var handle1 = arg0; + var rep2 = handleTable4[(handle1 << 1) + 1] & ~T_FLAG; + var rsc0 = captureTable4.get(rep2); + if (!rsc0) { + rsc0 = Object.create(Descriptor.prototype); + Object.defineProperty(rsc0, symbolRscHandle, { writable: true, value: handle1}); + Object.defineProperty(rsc0, symbolRscRep, { writable: true, value: rep2}); + } + let ret; + try { + ret = { tag: 'ok', val: rsc0.appendViaStream()}; + } catch (e) { + ret = { tag: 'err', val: getErrorPayload(e) }; + } + rsc0[symbolRscHandle] = null; + var variant5 = ret; + switch (variant5.tag) { + case 'ok': { + const e = variant5.val; + dataView(memory0).setInt8(arg1 + 0, 0, true); + if (!(e instanceof OutputStream)) { + throw new Error('Resource error: Not a valid "OutputStream" resource.'); + } + var handle3 = e[symbolRscHandle]; + + if (!handle3) { + const rep = e[symbolRscRep] || ++captureCnt2; + captureTable2.set(rep, e); + handle3 = rscTableCreateOwn(handleTable2, rep); + } + dataView(memory0).setInt32(arg1 + 4, handle3, true); + break; + } + case 'err': { + const e = variant5.val; + dataView(memory0).setInt8(arg1 + 0, 1, true); + var val4 = e; + let enum4; + switch (val4) { + case 'access': { + enum4 = 0; + break; + } + case 'would-block': { + enum4 = 1; + break; + } + case 'already': { + enum4 = 2; + break; + } + case 'bad-descriptor': { + enum4 = 3; + break; + } + case 'busy': { + enum4 = 4; + break; + } + case 'deadlock': { + enum4 = 5; + break; + } + case 'quota': { + enum4 = 6; + break; + } + case 'exist': { + enum4 = 7; + break; + } + case 'file-too-large': { + enum4 = 8; + break; + } + case 'illegal-byte-sequence': { + enum4 = 9; + break; + } + case 'in-progress': { + enum4 = 10; + break; + } + case 'interrupted': { + enum4 = 11; + break; + } + case 'invalid': { + enum4 = 12; + break; + } + case 'io': { + enum4 = 13; + break; + } + case 'is-directory': { + enum4 = 14; + break; + } + case 'loop': { + enum4 = 15; + break; + } + case 'too-many-links': { + enum4 = 16; + break; + } + case 'message-size': { + enum4 = 17; + break; + } + case 'name-too-long': { + enum4 = 18; + break; + } + case 'no-device': { + enum4 = 19; + break; + } + case 'no-entry': { + enum4 = 20; + break; + } + case 'no-lock': { + enum4 = 21; + break; + } + case 'insufficient-memory': { + enum4 = 22; + break; + } + case 'insufficient-space': { + enum4 = 23; + break; + } + case 'not-directory': { + enum4 = 24; + break; + } + case 'not-empty': { + enum4 = 25; + break; + } + case 'not-recoverable': { + enum4 = 26; + break; + } + case 'unsupported': { + enum4 = 27; + break; + } + case 'no-tty': { + enum4 = 28; + break; + } + case 'no-such-device': { + enum4 = 29; + break; + } + case 'overflow': { + enum4 = 30; + break; + } + case 'not-permitted': { + enum4 = 31; + break; + } + case 'pipe': { + enum4 = 32; + break; + } + case 'read-only': { + enum4 = 33; + break; + } + case 'invalid-seek': { + enum4 = 34; + break; + } + case 'text-file-busy': { + enum4 = 35; + break; + } + case 'cross-device': { + enum4 = 36; + break; + } + default: { + if ((e) instanceof Error) { + console.error(e); + } + + throw new TypeError(`"${val4}" is not one of the cases of error-code`); + } + } + dataView(memory0).setInt8(arg1 + 4, enum4, true); + break; + } + default: { + throw new TypeError('invalid variant specified for result'); + } + } +} + +function trampoline19(arg0, arg1) { + var handle1 = arg0; + var rep2 = handleTable4[(handle1 << 1) + 1] & ~T_FLAG; + var rsc0 = captureTable4.get(rep2); + if (!rsc0) { + rsc0 = Object.create(Descriptor.prototype); + Object.defineProperty(rsc0, symbolRscHandle, { writable: true, value: handle1}); + Object.defineProperty(rsc0, symbolRscRep, { writable: true, value: rep2}); + } + let ret; + try { + ret = { tag: 'ok', val: rsc0.getType()}; + } catch (e) { + ret = { tag: 'err', val: getErrorPayload(e) }; + } + rsc0[symbolRscHandle] = null; + var variant5 = ret; + switch (variant5.tag) { + case 'ok': { + const e = variant5.val; + dataView(memory0).setInt8(arg1 + 0, 0, true); + var val3 = e; + let enum3; + switch (val3) { + case 'unknown': { + enum3 = 0; + break; + } + case 'block-device': { + enum3 = 1; + break; + } + case 'character-device': { + enum3 = 2; + break; + } + case 'directory': { + enum3 = 3; + break; + } + case 'fifo': { + enum3 = 4; + break; + } + case 'symbolic-link': { + enum3 = 5; + break; + } + case 'regular-file': { + enum3 = 6; + break; + } + case 'socket': { + enum3 = 7; + break; + } + default: { + if ((e) instanceof Error) { + console.error(e); + } + + throw new TypeError(`"${val3}" is not one of the cases of descriptor-type`); + } + } + dataView(memory0).setInt8(arg1 + 1, enum3, true); + break; + } + case 'err': { + const e = variant5.val; + dataView(memory0).setInt8(arg1 + 0, 1, true); + var val4 = e; + let enum4; + switch (val4) { + case 'access': { + enum4 = 0; + break; + } + case 'would-block': { + enum4 = 1; + break; + } + case 'already': { + enum4 = 2; + break; + } + case 'bad-descriptor': { + enum4 = 3; + break; + } + case 'busy': { + enum4 = 4; + break; + } + case 'deadlock': { + enum4 = 5; + break; + } + case 'quota': { + enum4 = 6; + break; + } + case 'exist': { + enum4 = 7; + break; + } + case 'file-too-large': { + enum4 = 8; + break; + } + case 'illegal-byte-sequence': { + enum4 = 9; + break; + } + case 'in-progress': { + enum4 = 10; + break; + } + case 'interrupted': { + enum4 = 11; + break; + } + case 'invalid': { + enum4 = 12; + break; + } + case 'io': { + enum4 = 13; + break; + } + case 'is-directory': { + enum4 = 14; + break; + } + case 'loop': { + enum4 = 15; + break; + } + case 'too-many-links': { + enum4 = 16; + break; + } + case 'message-size': { + enum4 = 17; + break; + } + case 'name-too-long': { + enum4 = 18; + break; + } + case 'no-device': { + enum4 = 19; + break; + } + case 'no-entry': { + enum4 = 20; + break; + } + case 'no-lock': { + enum4 = 21; + break; + } + case 'insufficient-memory': { + enum4 = 22; + break; + } + case 'insufficient-space': { + enum4 = 23; + break; + } + case 'not-directory': { + enum4 = 24; + break; + } + case 'not-empty': { + enum4 = 25; + break; + } + case 'not-recoverable': { + enum4 = 26; + break; + } + case 'unsupported': { + enum4 = 27; + break; + } + case 'no-tty': { + enum4 = 28; + break; + } + case 'no-such-device': { + enum4 = 29; + break; + } + case 'overflow': { + enum4 = 30; + break; + } + case 'not-permitted': { + enum4 = 31; + break; + } + case 'pipe': { + enum4 = 32; + break; + } + case 'read-only': { + enum4 = 33; + break; + } + case 'invalid-seek': { + enum4 = 34; + break; + } + case 'text-file-busy': { + enum4 = 35; + break; + } + case 'cross-device': { + enum4 = 36; + break; + } + default: { + if ((e) instanceof Error) { + console.error(e); + } + + throw new TypeError(`"${val4}" is not one of the cases of error-code`); + } + } + dataView(memory0).setInt8(arg1 + 1, enum4, true); + break; + } + default: { + throw new TypeError('invalid variant specified for result'); + } + } +} + +function trampoline20(arg0, arg1) { + var handle1 = arg0; + var rep2 = handleTable4[(handle1 << 1) + 1] & ~T_FLAG; + var rsc0 = captureTable4.get(rep2); + if (!rsc0) { + rsc0 = Object.create(Descriptor.prototype); + Object.defineProperty(rsc0, symbolRscHandle, { writable: true, value: handle1}); + Object.defineProperty(rsc0, symbolRscRep, { writable: true, value: rep2}); + } + let ret; + try { + ret = { tag: 'ok', val: rsc0.stat()}; + } catch (e) { + ret = { tag: 'err', val: getErrorPayload(e) }; + } + rsc0[symbolRscHandle] = null; + var variant12 = ret; + switch (variant12.tag) { + case 'ok': { + const e = variant12.val; + dataView(memory0).setInt8(arg1 + 0, 0, true); + var {type: v3_0, linkCount: v3_1, size: v3_2, dataAccessTimestamp: v3_3, dataModificationTimestamp: v3_4, statusChangeTimestamp: v3_5 } = e; + var val4 = v3_0; + let enum4; + switch (val4) { + case 'unknown': { + enum4 = 0; + break; + } + case 'block-device': { + enum4 = 1; + break; + } + case 'character-device': { + enum4 = 2; + break; + } + case 'directory': { + enum4 = 3; + break; + } + case 'fifo': { + enum4 = 4; + break; + } + case 'symbolic-link': { + enum4 = 5; + break; + } + case 'regular-file': { + enum4 = 6; + break; + } + case 'socket': { + enum4 = 7; + break; + } + default: { + if ((v3_0) instanceof Error) { + console.error(v3_0); + } + + throw new TypeError(`"${val4}" is not one of the cases of descriptor-type`); + } + } + dataView(memory0).setInt8(arg1 + 8, enum4, true); + dataView(memory0).setBigInt64(arg1 + 16, toUint64(v3_1), true); + dataView(memory0).setBigInt64(arg1 + 24, toUint64(v3_2), true); + var variant6 = v3_3; + if (variant6 === null || variant6=== undefined) { + dataView(memory0).setInt8(arg1 + 32, 0, true); + } else { + const e = variant6; + dataView(memory0).setInt8(arg1 + 32, 1, true); + var {seconds: v5_0, nanoseconds: v5_1 } = e; + dataView(memory0).setBigInt64(arg1 + 40, toUint64(v5_0), true); + dataView(memory0).setInt32(arg1 + 48, toUint32(v5_1), true); + } + var variant8 = v3_4; + if (variant8 === null || variant8=== undefined) { + dataView(memory0).setInt8(arg1 + 56, 0, true); + } else { + const e = variant8; + dataView(memory0).setInt8(arg1 + 56, 1, true); + var {seconds: v7_0, nanoseconds: v7_1 } = e; + dataView(memory0).setBigInt64(arg1 + 64, toUint64(v7_0), true); + dataView(memory0).setInt32(arg1 + 72, toUint32(v7_1), true); + } + var variant10 = v3_5; + if (variant10 === null || variant10=== undefined) { + dataView(memory0).setInt8(arg1 + 80, 0, true); + } else { + const e = variant10; + dataView(memory0).setInt8(arg1 + 80, 1, true); + var {seconds: v9_0, nanoseconds: v9_1 } = e; + dataView(memory0).setBigInt64(arg1 + 88, toUint64(v9_0), true); + dataView(memory0).setInt32(arg1 + 96, toUint32(v9_1), true); + } + break; + } + case 'err': { + const e = variant12.val; + dataView(memory0).setInt8(arg1 + 0, 1, true); + var val11 = e; + let enum11; + switch (val11) { + case 'access': { + enum11 = 0; + break; + } + case 'would-block': { + enum11 = 1; + break; + } + case 'already': { + enum11 = 2; + break; + } + case 'bad-descriptor': { + enum11 = 3; + break; + } + case 'busy': { + enum11 = 4; + break; + } + case 'deadlock': { + enum11 = 5; + break; + } + case 'quota': { + enum11 = 6; + break; + } + case 'exist': { + enum11 = 7; + break; + } + case 'file-too-large': { + enum11 = 8; + break; + } + case 'illegal-byte-sequence': { + enum11 = 9; + break; + } + case 'in-progress': { + enum11 = 10; + break; + } + case 'interrupted': { + enum11 = 11; + break; + } + case 'invalid': { + enum11 = 12; + break; + } + case 'io': { + enum11 = 13; + break; + } + case 'is-directory': { + enum11 = 14; + break; + } + case 'loop': { + enum11 = 15; + break; + } + case 'too-many-links': { + enum11 = 16; + break; + } + case 'message-size': { + enum11 = 17; + break; + } + case 'name-too-long': { + enum11 = 18; + break; + } + case 'no-device': { + enum11 = 19; + break; + } + case 'no-entry': { + enum11 = 20; + break; + } + case 'no-lock': { + enum11 = 21; + break; + } + case 'insufficient-memory': { + enum11 = 22; + break; + } + case 'insufficient-space': { + enum11 = 23; + break; + } + case 'not-directory': { + enum11 = 24; + break; + } + case 'not-empty': { + enum11 = 25; + break; + } + case 'not-recoverable': { + enum11 = 26; + break; + } + case 'unsupported': { + enum11 = 27; + break; + } + case 'no-tty': { + enum11 = 28; + break; + } + case 'no-such-device': { + enum11 = 29; + break; + } + case 'overflow': { + enum11 = 30; + break; + } + case 'not-permitted': { + enum11 = 31; + break; + } + case 'pipe': { + enum11 = 32; + break; + } + case 'read-only': { + enum11 = 33; + break; + } + case 'invalid-seek': { + enum11 = 34; + break; + } + case 'text-file-busy': { + enum11 = 35; + break; + } + case 'cross-device': { + enum11 = 36; + break; + } + default: { + if ((e) instanceof Error) { + console.error(e); + } + + throw new TypeError(`"${val11}" is not one of the cases of error-code`); + } + } + dataView(memory0).setInt8(arg1 + 8, enum11, true); + break; + } + default: { + throw new TypeError('invalid variant specified for result'); + } + } +} +const handleTable1 = [T_FLAG, 0]; +const captureTable1= new Map(); +let captureCnt1 = 0; +handleTables[1] = handleTable1; + +function trampoline21(arg0, arg1) { + var handle1 = arg0; + var rep2 = handleTable1[(handle1 << 1) + 1] & ~T_FLAG; + var rsc0 = captureTable1.get(rep2); + if (!rsc0) { + rsc0 = Object.create(Error$1.prototype); + Object.defineProperty(rsc0, symbolRscHandle, { writable: true, value: handle1}); + Object.defineProperty(rsc0, symbolRscRep, { writable: true, value: rep2}); + } + const ret = filesystemErrorCode(rsc0); + rsc0[symbolRscHandle] = null; + var variant4 = ret; + if (variant4 === null || variant4=== undefined) { + dataView(memory0).setInt8(arg1 + 0, 0, true); + } else { + const e = variant4; + dataView(memory0).setInt8(arg1 + 0, 1, true); + var val3 = e; + let enum3; + switch (val3) { + case 'access': { + enum3 = 0; + break; + } + case 'would-block': { + enum3 = 1; + break; + } + case 'already': { + enum3 = 2; + break; + } + case 'bad-descriptor': { + enum3 = 3; + break; + } + case 'busy': { + enum3 = 4; + break; + } + case 'deadlock': { + enum3 = 5; + break; + } + case 'quota': { + enum3 = 6; + break; + } + case 'exist': { + enum3 = 7; + break; + } + case 'file-too-large': { + enum3 = 8; + break; + } + case 'illegal-byte-sequence': { + enum3 = 9; + break; + } + case 'in-progress': { + enum3 = 10; + break; + } + case 'interrupted': { + enum3 = 11; + break; + } + case 'invalid': { + enum3 = 12; + break; + } + case 'io': { + enum3 = 13; + break; + } + case 'is-directory': { + enum3 = 14; + break; + } + case 'loop': { + enum3 = 15; + break; + } + case 'too-many-links': { + enum3 = 16; + break; + } + case 'message-size': { + enum3 = 17; + break; + } + case 'name-too-long': { + enum3 = 18; + break; + } + case 'no-device': { + enum3 = 19; + break; + } + case 'no-entry': { + enum3 = 20; + break; + } + case 'no-lock': { + enum3 = 21; + break; + } + case 'insufficient-memory': { + enum3 = 22; + break; + } + case 'insufficient-space': { + enum3 = 23; + break; + } + case 'not-directory': { + enum3 = 24; + break; + } + case 'not-empty': { + enum3 = 25; + break; + } + case 'not-recoverable': { + enum3 = 26; + break; + } + case 'unsupported': { + enum3 = 27; + break; + } + case 'no-tty': { + enum3 = 28; + break; + } + case 'no-such-device': { + enum3 = 29; + break; + } + case 'overflow': { + enum3 = 30; + break; + } + case 'not-permitted': { + enum3 = 31; + break; + } + case 'pipe': { + enum3 = 32; + break; + } + case 'read-only': { + enum3 = 33; + break; + } + case 'invalid-seek': { + enum3 = 34; + break; + } + case 'text-file-busy': { + enum3 = 35; + break; + } + case 'cross-device': { + enum3 = 36; + break; + } + default: { + if ((e) instanceof Error) { + console.error(e); + } + + throw new TypeError(`"${val3}" is not one of the cases of error-code`); + } + } + dataView(memory0).setInt8(arg1 + 1, enum3, true); + } +} + +function trampoline22(arg0, arg1) { + var handle1 = arg0; + var rep2 = handleTable2[(handle1 << 1) + 1] & ~T_FLAG; + var rsc0 = captureTable2.get(rep2); + if (!rsc0) { + rsc0 = Object.create(OutputStream.prototype); + Object.defineProperty(rsc0, symbolRscHandle, { writable: true, value: handle1}); + Object.defineProperty(rsc0, symbolRscRep, { writable: true, value: rep2}); + } + let ret; + try { + ret = { tag: 'ok', val: rsc0.checkWrite()}; + } catch (e) { + ret = { tag: 'err', val: getErrorPayload(e) }; + } + rsc0[symbolRscHandle] = null; + var variant5 = ret; + switch (variant5.tag) { + case 'ok': { + const e = variant5.val; + dataView(memory0).setInt8(arg1 + 0, 0, true); + dataView(memory0).setBigInt64(arg1 + 8, toUint64(e), true); + break; + } + case 'err': { + const e = variant5.val; + dataView(memory0).setInt8(arg1 + 0, 1, true); + var variant4 = e; + switch (variant4.tag) { + case 'last-operation-failed': { + const e = variant4.val; + dataView(memory0).setInt8(arg1 + 8, 0, true); + if (!(e instanceof Error$1)) { + throw new Error('Resource error: Not a valid "Error" resource.'); + } + var handle3 = e[symbolRscHandle]; + + if (!handle3) { + const rep = e[symbolRscRep] || ++captureCnt1; + captureTable1.set(rep, e); + handle3 = rscTableCreateOwn(handleTable1, rep); + } + dataView(memory0).setInt32(arg1 + 12, handle3, true); + break; + } + case 'closed': { + dataView(memory0).setInt8(arg1 + 8, 1, true); + break; + } + default: { + throw new TypeError(`invalid variant tag value \`${JSON.stringify(variant4.tag)}\` (received \`${variant4}\`) specified for \`StreamError\``); + } + } + break; + } + default: { + throw new TypeError('invalid variant specified for result'); + } + } +} + +function trampoline23(arg0, arg1, arg2, arg3) { + var handle1 = arg0; + var rep2 = handleTable2[(handle1 << 1) + 1] & ~T_FLAG; + var rsc0 = captureTable2.get(rep2); + if (!rsc0) { + rsc0 = Object.create(OutputStream.prototype); + Object.defineProperty(rsc0, symbolRscHandle, { writable: true, value: handle1}); + Object.defineProperty(rsc0, symbolRscRep, { writable: true, value: rep2}); + } + var ptr3 = arg1; + var len3 = arg2; + var result3 = new Uint8Array(memory0.buffer.slice(ptr3, ptr3 + len3 * 1)); + let ret; + try { + ret = { tag: 'ok', val: rsc0.write(result3)}; + } catch (e) { + ret = { tag: 'err', val: getErrorPayload(e) }; + } + rsc0[symbolRscHandle] = null; + var variant6 = ret; + switch (variant6.tag) { + case 'ok': { + const e = variant6.val; + dataView(memory0).setInt8(arg3 + 0, 0, true); + break; + } + case 'err': { + const e = variant6.val; + dataView(memory0).setInt8(arg3 + 0, 1, true); + var variant5 = e; + switch (variant5.tag) { + case 'last-operation-failed': { + const e = variant5.val; + dataView(memory0).setInt8(arg3 + 4, 0, true); + if (!(e instanceof Error$1)) { + throw new Error('Resource error: Not a valid "Error" resource.'); + } + var handle4 = e[symbolRscHandle]; + + if (!handle4) { + const rep = e[symbolRscRep] || ++captureCnt1; + captureTable1.set(rep, e); + handle4 = rscTableCreateOwn(handleTable1, rep); + } + dataView(memory0).setInt32(arg3 + 8, handle4, true); + break; + } + case 'closed': { + dataView(memory0).setInt8(arg3 + 4, 1, true); + break; + } + default: { + throw new TypeError(`invalid variant tag value \`${JSON.stringify(variant5.tag)}\` (received \`${variant5}\`) specified for \`StreamError\``); + } + } + break; + } + default: { + throw new TypeError('invalid variant specified for result'); + } + } +} + +function trampoline24(arg0, arg1, arg2, arg3) { + var handle1 = arg0; + var rep2 = handleTable2[(handle1 << 1) + 1] & ~T_FLAG; + var rsc0 = captureTable2.get(rep2); + if (!rsc0) { + rsc0 = Object.create(OutputStream.prototype); + Object.defineProperty(rsc0, symbolRscHandle, { writable: true, value: handle1}); + Object.defineProperty(rsc0, symbolRscRep, { writable: true, value: rep2}); + } + var ptr3 = arg1; + var len3 = arg2; + var result3 = new Uint8Array(memory0.buffer.slice(ptr3, ptr3 + len3 * 1)); + let ret; + try { + ret = { tag: 'ok', val: rsc0.blockingWriteAndFlush(result3)}; + } catch (e) { + ret = { tag: 'err', val: getErrorPayload(e) }; + } + rsc0[symbolRscHandle] = null; + var variant6 = ret; + switch (variant6.tag) { + case 'ok': { + const e = variant6.val; + dataView(memory0).setInt8(arg3 + 0, 0, true); + break; + } + case 'err': { + const e = variant6.val; + dataView(memory0).setInt8(arg3 + 0, 1, true); + var variant5 = e; + switch (variant5.tag) { + case 'last-operation-failed': { + const e = variant5.val; + dataView(memory0).setInt8(arg3 + 4, 0, true); + if (!(e instanceof Error$1)) { + throw new Error('Resource error: Not a valid "Error" resource.'); + } + var handle4 = e[symbolRscHandle]; + + if (!handle4) { + const rep = e[symbolRscRep] || ++captureCnt1; + captureTable1.set(rep, e); + handle4 = rscTableCreateOwn(handleTable1, rep); + } + dataView(memory0).setInt32(arg3 + 8, handle4, true); + break; + } + case 'closed': { + dataView(memory0).setInt8(arg3 + 4, 1, true); + break; + } + default: { + throw new TypeError(`invalid variant tag value \`${JSON.stringify(variant5.tag)}\` (received \`${variant5}\`) specified for \`StreamError\``); + } + } + break; + } + default: { + throw new TypeError('invalid variant specified for result'); + } + } +} + +function trampoline25(arg0, arg1) { + var handle1 = arg0; + var rep2 = handleTable2[(handle1 << 1) + 1] & ~T_FLAG; + var rsc0 = captureTable2.get(rep2); + if (!rsc0) { + rsc0 = Object.create(OutputStream.prototype); + Object.defineProperty(rsc0, symbolRscHandle, { writable: true, value: handle1}); + Object.defineProperty(rsc0, symbolRscRep, { writable: true, value: rep2}); + } + let ret; + try { + ret = { tag: 'ok', val: rsc0.blockingFlush()}; + } catch (e) { + ret = { tag: 'err', val: getErrorPayload(e) }; + } + rsc0[symbolRscHandle] = null; + var variant5 = ret; + switch (variant5.tag) { + case 'ok': { + const e = variant5.val; + dataView(memory0).setInt8(arg1 + 0, 0, true); + break; + } + case 'err': { + const e = variant5.val; + dataView(memory0).setInt8(arg1 + 0, 1, true); + var variant4 = e; + switch (variant4.tag) { + case 'last-operation-failed': { + const e = variant4.val; + dataView(memory0).setInt8(arg1 + 4, 0, true); + if (!(e instanceof Error$1)) { + throw new Error('Resource error: Not a valid "Error" resource.'); + } + var handle3 = e[symbolRscHandle]; + + if (!handle3) { + const rep = e[symbolRscRep] || ++captureCnt1; + captureTable1.set(rep, e); + handle3 = rscTableCreateOwn(handleTable1, rep); + } + dataView(memory0).setInt32(arg1 + 8, handle3, true); + break; + } + case 'closed': { + dataView(memory0).setInt8(arg1 + 4, 1, true); + break; + } + default: { + throw new TypeError(`invalid variant tag value \`${JSON.stringify(variant4.tag)}\` (received \`${variant4}\`) specified for \`StreamError\``); + } + } + break; + } + default: { + throw new TypeError('invalid variant specified for result'); + } + } +} + +function trampoline26(arg0) { + const ret = getEnvironment(); + var vec3 = ret; + var len3 = vec3.length; + var result3 = realloc0(0, 0, 4, len3 * 16); + for (let i = 0; i < vec3.length; i++) { + const e = vec3[i]; + const base = result3 + i * 16;var [tuple0_0, tuple0_1] = e; + var ptr1 = utf8Encode(tuple0_0, realloc0, memory0); + var len1 = utf8EncodedLen; + dataView(memory0).setInt32(base + 4, len1, true); + dataView(memory0).setInt32(base + 0, ptr1, true); + var ptr2 = utf8Encode(tuple0_1, realloc0, memory0); + var len2 = utf8EncodedLen; + dataView(memory0).setInt32(base + 12, len2, true); + dataView(memory0).setInt32(base + 8, ptr2, true); + } + dataView(memory0).setInt32(arg0 + 4, len3, true); + dataView(memory0).setInt32(arg0 + 0, result3, true); +} +let exports3; +const handleTable5 = [T_FLAG, 0]; +const finalizationRegistry5= new FinalizationRegistry((handle) => { + const { rep } = rscTableRemove(handleTable5, handle); + exports0['15'](rep); +}); + +handleTables[5] = handleTable5; +const trampoline0 = rscTableCreateOwn.bind(null, handleTable5); +function trampoline1(handle) { + const handleEntry = rscTableRemove(handleTable0, handle); + if (!handleEntry.own) throw new Error('Internal error: Unexpected borrow handle'); + const rsc = captureTable0.get(handleEntry.rep); + if (rsc) { + if (rsc[symbolDispose]) rsc[symbolDispose](); + captureTable0.delete(handleEntry.rep); + } else if (R[symbolCabiDispose]) { + R[symbolCabiDispose](handleEntry.rep); + } +} +function trampoline7(handle) { + const handleEntry = rscTableRemove(handleTable5, handle); + if (!handleEntry.own) throw new Error('Internal error: Unexpected borrow handle'); + exports0['15'](handleEntry.rep); +} +function trampoline8(handle) { + const handleEntry = rscTableRemove(handleTable1, handle); + if (!handleEntry.own) throw new Error('Internal error: Unexpected borrow handle'); + const rsc = captureTable1.get(handleEntry.rep); + if (rsc) { + if (rsc[symbolDispose]) rsc[symbolDispose](); + captureTable1.delete(handleEntry.rep); + } else if (Error$1[symbolCabiDispose]) { + Error$1[symbolCabiDispose](handleEntry.rep); + } +} +function trampoline9(handle) { + const handleEntry = rscTableRemove(handleTable3, handle); + if (!handleEntry.own) throw new Error('Internal error: Unexpected borrow handle'); + const rsc = captureTable3.get(handleEntry.rep); + if (rsc) { + if (rsc[symbolDispose]) rsc[symbolDispose](); + captureTable3.delete(handleEntry.rep); + } else if (InputStream[symbolCabiDispose]) { + InputStream[symbolCabiDispose](handleEntry.rep); + } +} +function trampoline10(handle) { + const handleEntry = rscTableRemove(handleTable2, handle); + if (!handleEntry.own) throw new Error('Internal error: Unexpected borrow handle'); + const rsc = captureTable2.get(handleEntry.rep); + if (rsc) { + if (rsc[symbolDispose]) rsc[symbolDispose](); + captureTable2.delete(handleEntry.rep); + } else if (OutputStream[symbolCabiDispose]) { + OutputStream[symbolCabiDispose](handleEntry.rep); + } +} +function trampoline11(handle) { + const handleEntry = rscTableRemove(handleTable4, handle); + if (!handleEntry.own) throw new Error('Internal error: Unexpected borrow handle'); + const rsc = captureTable4.get(handleEntry.rep); + if (rsc) { + if (rsc[symbolDispose]) rsc[symbolDispose](); + captureTable4.delete(handleEntry.rep); + } else if (Descriptor[symbolCabiDispose]) { + Descriptor[symbolCabiDispose](handleEntry.rep); + } +} + +class R$1{ + constructor(arg0) { + const ret = exports1['foo:foo/resources#[constructor]r'](toUint32(arg0)); + var handle1 = ret; + var rsc0 = new.target === R$1 ? this : Object.create(R$1.prototype); + var rep2 = handleTable5[(handle1 << 1) + 1] & ~T_FLAG; + Object.defineProperty(rsc0, symbolRscHandle, { writable: true, value: rep2}); + finalizationRegistry5.register(rsc0, handle1, rsc0); + Object.defineProperty(rsc0, symbolDispose, { writable: true, value: function () { + finalizationRegistry5.unregister(rsc0); + rscTableRemove(handleTable5, handle1); + rsc0[symbolDispose] = emptyFunc; + rsc0[symbolRscHandle] = null; + exports0['15'](rep2); + } }); + rscTableRemove(handleTable5, handle1); + + return rsc0; + } +} + +R$1.prototype.add = function add(arg1) { + var handle0 = this[symbolRscHandle]; + if (!handle0) { + throw new Error('Resource error: Not a valid "R" resource.'); + } + + exports1['foo:foo/resources#[method]r.add'](handle0, toUint32(arg1)); +}; + +function create$1() { + const ret = exports1['foo:foo/resources#create'](); + var handle1 = ret; + var rsc0 = new.target === R$1 ? this : Object.create(R$1.prototype); + var rep2 = handleTable5[(handle1 << 1) + 1] & ~T_FLAG; + Object.defineProperty(rsc0, symbolRscHandle, { writable: true, value: rep2}); + finalizationRegistry5.register(rsc0, handle1, rsc0); + Object.defineProperty(rsc0, symbolDispose, { writable: true, value: function () { + finalizationRegistry5.unregister(rsc0); + rscTableRemove(handleTable5, handle1); + rsc0[symbolDispose] = emptyFunc; + rsc0[symbolRscHandle] = null; + exports0['15'](rep2); + } }); + rscTableRemove(handleTable5, handle1); + + return rsc0; +} + +function borrows$1(arg0) { + var handle0 = arg0[symbolRscHandle]; + if (!handle0) { + throw new Error('Resource error: Not a valid "R" resource.'); + } + + exports1['foo:foo/resources#borrows'](handle0); +} + +function consume$1(arg0) { + var handle0 = arg0[symbolRscHandle]; + if (!handle0) { + throw new Error('Resource error: Not a valid "R" resource.'); + } + + finalizationRegistry5.unregister(arg0); + arg0[symbolDispose] = emptyFunc; + arg0[symbolRscHandle] = null; + exports1['foo:foo/resources#consume'](handle0); +} + +function unimplemented() {todo();} +function fd_stat_error() { return -1; } + +const $init = (async() => { + const module0 = fetchCompile(new URL('./component.core.wasm', import.meta.url)); + const module1 = fetchCompile(new URL('./component.core2.wasm', import.meta.url)); + const module2 = base64Compile('AGFzbQEAAAABKQdgAX8AYAN/fn8AYAJ/fwBgBH9/f38AYAR/f39/AX9gAn9/AX9gAX8AAxEQAAECAgICAgMDAgAEBQUGBgQFAXABEBAHUhEBMAAAATEAAQEyAAIBMwADATQABAE1AAUBNgAGATcABwE4AAgBOQAJAjEwAAoCMTEACwIxMgAMAjEzAA0CMTQADgIxNQAPCCRpbXBvcnRzAQAKxwEQCQAgAEEAEQAACw0AIAAgASACQQERAQALCwAgACABQQIRAgALCwAgACABQQMRAgALCwAgACABQQQRAgALCwAgACABQQURAgALCwAgACABQQYRAgALDwAgACABIAIgA0EHEQMACw8AIAAgASACIANBCBEDAAsLACAAIAFBCRECAAsJACAAQQoRAAALDwAgACABIAIgA0ELEQQACwsAIAAgAUEMEQUACwsAIAAgAUENEQUACwkAIABBDhEGAAsJACAAQQ8RBgALAC8JcHJvZHVjZXJzAQxwcm9jZXNzZWQtYnkBDXdpdC1jb21wb25lbnQHMC4yMDAuMAC6BwRuYW1lABMSd2l0LWNvbXBvbmVudDpzaGltAZ0HEAA3aW5kaXJlY3Qtd2FzaTpmaWxlc3lzdGVtL3ByZW9wZW5zQDAuMi4wLWdldC1kaXJlY3RvcmllcwFIaW5kaXJlY3Qtd2FzaTpmaWxlc3lzdGVtL3R5cGVzQDAuMi4wLVttZXRob2RdZGVzY3JpcHRvci53cml0ZS12aWEtc3RyZWFtAklpbmRpcmVjdC13YXNpOmZpbGVzeXN0ZW0vdHlwZXNAMC4yLjAtW21ldGhvZF1kZXNjcmlwdG9yLmFwcGVuZC12aWEtc3RyZWFtA0BpbmRpcmVjdC13YXNpOmZpbGVzeXN0ZW0vdHlwZXNAMC4yLjAtW21ldGhvZF1kZXNjcmlwdG9yLmdldC10eXBlBDxpbmRpcmVjdC13YXNpOmZpbGVzeXN0ZW0vdHlwZXNAMC4yLjAtW21ldGhvZF1kZXNjcmlwdG9yLnN0YXQFOmluZGlyZWN0LXdhc2k6ZmlsZXN5c3RlbS90eXBlc0AwLjIuMC1maWxlc3lzdGVtLWVycm9yLWNvZGUGQGluZGlyZWN0LXdhc2k6aW8vc3RyZWFtc0AwLjIuMC1bbWV0aG9kXW91dHB1dC1zdHJlYW0uY2hlY2std3JpdGUHOmluZGlyZWN0LXdhc2k6aW8vc3RyZWFtc0AwLjIuMC1bbWV0aG9kXW91dHB1dC1zdHJlYW0ud3JpdGUITWluZGlyZWN0LXdhc2k6aW8vc3RyZWFtc0AwLjIuMC1bbWV0aG9kXW91dHB1dC1zdHJlYW0uYmxvY2tpbmctd3JpdGUtYW5kLWZsdXNoCUNpbmRpcmVjdC13YXNpOmlvL3N0cmVhbXNAMC4yLjAtW21ldGhvZF1vdXRwdXQtc3RyZWFtLmJsb2NraW5nLWZsdXNoCjNpbmRpcmVjdC13YXNpOmNsaS9lbnZpcm9ubWVudEAwLjIuMC1nZXQtZW52aXJvbm1lbnQLJWFkYXB0LXdhc2lfc25hcHNob3RfcHJldmlldzEtZmRfd3JpdGUMKGFkYXB0LXdhc2lfc25hcHNob3RfcHJldmlldzEtZW52aXJvbl9nZXQNLmFkYXB0LXdhc2lfc25hcHNob3RfcHJldmlldzEtZW52aXJvbl9zaXplc19nZXQOJmFkYXB0LXdhc2lfc25hcHNob3RfcHJldmlldzEtcHJvY19leGl0DyBkdG9yLVtleHBvcnRdZm9vOmZvby9yZXNvdXJjZXMtcg'); + const module3 = base64Compile('AGFzbQEAAAABKQdgAX8AYAN/fn8AYAJ/fwBgBH9/f38AYAR/f39/AX9gAn9/AX9gAX8AAmYRAAEwAAAAATEAAQABMgACAAEzAAIAATQAAgABNQACAAE2AAIAATcAAwABOAADAAE5AAIAAjEwAAAAAjExAAQAAjEyAAUAAjEzAAUAAjE0AAYAAjE1AAYACCRpbXBvcnRzAXABEBAJFgEAQQALEAABAgMEBQYHCAkKCwwNDg8ALwlwcm9kdWNlcnMBDHByb2Nlc3NlZC1ieQENd2l0LWNvbXBvbmVudAcwLjIwMC4wABwEbmFtZQAVFHdpdC1jb21wb25lbnQ6Zml4dXBz'); + ({ exports: exports0 } = await instantiateCore(await module2)); + ({ exports: exports1 } = await instantiateCore(await module0, { + '[export]foo:foo/resources': { + '[resource-drop]r': trampoline7, + '[resource-new]r': trampoline0, + }, + 'foo:foo/resources': { + '[constructor]r': trampoline2, + '[method]r.add': trampoline3, + '[resource-drop]r': trampoline1, + borrows: trampoline5, + consume: trampoline6, + create: trampoline4, + }, + wasi_snapshot_preview1: { + environ_get: exports0['12'], + environ_sizes_get: exports0['13'], + fd_write: exports0['11'], + proc_exit: exports0['14'], + args_get: unimplemented, + args_sizes_get: unimplemented, + fd_close: unimplemented, + fd_fdstat_get: fd_stat_error, + fd_seek: unimplemented, + }, + })); + ({ exports: exports2 } = await instantiateCore(await module1, { + __main_module__: { + cabi_realloc: exports1.cabi_realloc, + }, + env: { + memory: exports1.memory, + }, + 'wasi:cli/environment@0.2.0': { + 'get-environment': exports0['10'], + }, + 'wasi:cli/exit@0.2.0': { + exit: trampoline13, + }, + 'wasi:cli/stderr@0.2.0': { + 'get-stderr': trampoline12, + }, + 'wasi:cli/stdin@0.2.0': { + 'get-stdin': trampoline14, + }, + 'wasi:cli/stdout@0.2.0': { + 'get-stdout': trampoline15, + }, + 'wasi:filesystem/preopens@0.2.0': { + 'get-directories': exports0['0'], + }, + 'wasi:filesystem/types@0.2.0': { + '[method]descriptor.append-via-stream': exports0['2'], + '[method]descriptor.get-type': exports0['3'], + '[method]descriptor.stat': exports0['4'], + '[method]descriptor.write-via-stream': exports0['1'], + '[resource-drop]descriptor': trampoline11, + 'filesystem-error-code': exports0['5'], + }, + 'wasi:io/error@0.2.0': { + '[resource-drop]error': trampoline8, + }, + 'wasi:io/streams@0.2.0': { + '[method]output-stream.blocking-flush': exports0['9'], + '[method]output-stream.blocking-write-and-flush': exports0['8'], + '[method]output-stream.check-write': exports0['6'], + '[method]output-stream.write': exports0['7'], + '[resource-drop]input-stream': trampoline9, + '[resource-drop]output-stream': trampoline10, + }, + })); + memory0 = exports1.memory; + realloc0 = exports2.cabi_import_realloc; + ({ exports: exports3 } = await instantiateCore(await module3, { + '': { + $imports: exports0.$imports, + '0': trampoline16, + '1': trampoline17, + '10': trampoline26, + '11': exports2.fd_write, + '12': exports2.environ_get, + '13': exports2.environ_sizes_get, + '14': exports2.proc_exit, + '15': exports1['foo:foo/resources#[dtor]r'], + '2': trampoline18, + '3': trampoline19, + '4': trampoline20, + '5': trampoline21, + '6': trampoline22, + '7': trampoline23, + '8': trampoline24, + '9': trampoline25, + }, + })); +})(); + +await $init; +const resources = { + R: R$1, + borrows: borrows$1, + consume: consume$1, + create: create$1, + +}; + +export { resources, resources as 'foo:foo/resources', } \ No newline at end of file diff --git a/crates/cpp/tests/native_resources/rust/html/index.html b/crates/cpp/tests/native_resources/rust/html/index.html new file mode 100644 index 000000000..ff6ecda74 --- /dev/null +++ b/crates/cpp/tests/native_resources/rust/html/index.html @@ -0,0 +1,15 @@ + + + + + + + + + + +
Look into console
+ + + + \ No newline at end of file diff --git a/crates/cpp/tests/native_resources/rust/html/main.js b/crates/cpp/tests/native_resources/rust/html/main.js new file mode 100644 index 000000000..9d7f4b003 --- /dev/null +++ b/crates/cpp/tests/native_resources/rust/html/main.js @@ -0,0 +1,8 @@ +import {resources} from './component.js' + +var r = resources.create(); +r.add(12); +resources.borrows(r); +resources.consume(r); +let s = new resources.R(42); +s = null; diff --git a/crates/cpp/tests/native_resources/rust/html/resources.js b/crates/cpp/tests/native_resources/rust/html/resources.js new file mode 100644 index 000000000..a3be53446 --- /dev/null +++ b/crates/cpp/tests/native_resources/rust/html/resources.js @@ -0,0 +1,21 @@ + +export class R { + constructor(value) { + this.value = value; + } + add(b) { + this.value += b; + } +} + +export function borrows(obj) { + console.log('borrows', obj.value); +} + +export function consume(obj) { + console.log('consume', obj.value); +} + +export function create() { + return new R(1); +} diff --git a/crates/cpp/tests/native_resources/rust/src/lib.rs b/crates/cpp/tests/native_resources/rust/src/lib.rs new file mode 100644 index 000000000..754aa6986 --- /dev/null +++ b/crates/cpp/tests/native_resources/rust/src/lib.rs @@ -0,0 +1,44 @@ +use the_world::exports::foo::foo::resources::{self, Guest, GuestR, RBorrow}; +use core::alloc::Layout; +use std::sync::Mutex; + +mod the_world; + +#[derive(Debug)] +struct MyResource(Mutex); + +impl GuestR for MyResource { + fn new(a: u32) -> Self { + MyResource(Mutex::new(a)) + } + + fn add(&self, b: u32) { + *self.0.lock().unwrap() += b; + } +} + +struct MyWorld; + +impl Guest for MyWorld { + type R = MyResource; + + fn create() -> resources::R { + resources::R::new(MyResource::new(1)) + } + fn borrows(o: RBorrow<'_>) { + println!("resource borrowed with {:?}", o.get::().0.lock().unwrap()); + } + fn consume(o: resources::R) { + println!("resource consumed with {:?}", o.get::().0.lock().unwrap()); + + println!("exercise the other direction"); + let obj = the_world::foo::foo::resources::create(); + obj.add(12); + the_world::foo::foo::resources::borrows(&obj); + the_world::foo::foo::resources::consume(obj); + let obj2 = the_world::foo::foo::resources::R::new(42); + drop(obj2); + } +} + +the_world::export!(MyWorld with_types_in the_world); diff --git a/crates/cpp/tests/native_resources/rust/src/the_world.rs b/crates/cpp/tests/native_resources/rust/src/the_world.rs new file mode 100644 index 000000000..c80b4a3a4 --- /dev/null +++ b/crates/cpp/tests/native_resources/rust/src/the_world.rs @@ -0,0 +1,622 @@ +// Generated by `wit-bindgen` 0.24.0. DO NOT EDIT! +// Options used: +#[allow(dead_code)] +pub mod foo { + #[allow(dead_code)] + pub mod foo { + #[allow(dead_code, clippy::all)] + pub mod resources { + #[used] + #[doc(hidden)] + #[cfg(target_arch = "wasm32")] + static __FORCE_SECTION_REF: fn() = super::super::super::__link_custom_section_describing_imports; + use super::super::super::_rt; + + #[derive(Debug)] + #[repr(transparent)] + pub struct R{ + handle: _rt::Resource, + } + + impl R{ + #[doc(hidden)] + pub unsafe fn from_handle(handle: u32) -> Self { + Self { + handle: _rt::Resource::from_handle(handle), + } + } + + #[doc(hidden)] + pub fn take_handle(&self) -> u32 { + _rt::Resource::take_handle(&self.handle) + } + + #[doc(hidden)] + pub fn handle(&self) -> u32 { + _rt::Resource::handle(&self.handle) + } + } + + + unsafe impl _rt::WasmResource for R{ + #[inline] + unsafe fn drop(_handle: u32) { + { + #[link(wasm_import_module = "foo:foo/resources")] + extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "[resource-drop]r")] + fn fooX3AfooX2FresourcesX00X5Bresource_dropX5Dr(_: u32); + } + + fooX3AfooX2FresourcesX00X5Bresource_dropX5Dr(_handle); + } + } + } + + impl R { + #[allow(unused_unsafe, clippy::all)] + pub fn new(a: u32,) -> Self{ + unsafe { + + #[link(wasm_import_module = "foo:foo/resources")] + extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "[constructor]r")] + fn fooX3AfooX2FresourcesX00X5BconstructorX5Dr(_: i32, ) -> i32; + } + let ret = fooX3AfooX2FresourcesX00X5BconstructorX5Dr(_rt::as_i32(&a)); + R::from_handle(ret as u32) + } + } + } + impl R { + #[allow(unused_unsafe, clippy::all)] + pub fn add(&self,b: u32,){ + unsafe { + + #[link(wasm_import_module = "foo:foo/resources")] + extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "[method]r.add")] + fn fooX3AfooX2FresourcesX00X5BmethodX5DrX2Eadd(_: i32, _: i32, ); + } + fooX3AfooX2FresourcesX00X5BmethodX5DrX2Eadd((self).handle() as i32, _rt::as_i32(&b)); + } + } + } + #[allow(unused_unsafe, clippy::all)] + pub fn create() -> R{ + unsafe { + + #[link(wasm_import_module = "foo:foo/resources")] + extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "create")] + fn fooX3AfooX2FresourcesX00create() -> i32; + } + let ret = fooX3AfooX2FresourcesX00create(); + R::from_handle(ret as u32) + } + } + #[allow(unused_unsafe, clippy::all)] + pub fn borrows(o: &R,){ + unsafe { + + #[link(wasm_import_module = "foo:foo/resources")] + extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "borrows")] + fn fooX3AfooX2FresourcesX00borrows(_: i32, ); + } + fooX3AfooX2FresourcesX00borrows((o).handle() as i32); + } + } + #[allow(unused_unsafe, clippy::all)] + pub fn consume(o: R,){ + unsafe { + + #[link(wasm_import_module = "foo:foo/resources")] + extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "consume")] + fn fooX3AfooX2FresourcesX00consume(_: i32, ); + } + fooX3AfooX2FresourcesX00consume((&o).take_handle() as i32); + } + } + + } + + } +} +#[allow(dead_code)] +pub mod exports { + #[allow(dead_code)] + pub mod foo { + #[allow(dead_code)] + pub mod foo { + #[allow(dead_code, clippy::all)] + pub mod resources { + #[used] + #[doc(hidden)] + #[cfg(target_arch = "wasm32")] + static __FORCE_SECTION_REF: fn() = super::super::super::super::__link_custom_section_describing_imports; + use super::super::super::super::_rt; + + #[derive(Debug)] + #[repr(transparent)] + pub struct R{ + handle: _rt::Resource, + } + + type _RRep = Option; + + impl R{ + /// Creates a new resource from the specified representation. + /// + /// This function will create a new resource handle by moving `val` onto + /// the heap and then passing that heap pointer to the component model to + /// create a handle. The owned handle is then returned as `R`. + pub fn new(val: T) -> Self { + Self::type_guard::(); + let val: _RRep = Some(val); + let ptr: *mut _RRep = + _rt::Box::into_raw(_rt::Box::new(val)); + unsafe { + Self::from_handle(T::_resource_new(ptr.cast())) + } + } + + /// Gets access to the underlying `T` which represents this resource. + pub fn get(&self) -> &T { + let ptr = unsafe { &*self.as_ptr::() }; + ptr.as_ref().unwrap() + } + + /// Gets mutable access to the underlying `T` which represents this + /// resource. + pub fn get_mut(&mut self) -> &mut T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.as_mut().unwrap() + } + + /// Consumes this resource and returns the underlying `T`. + pub fn into_inner(self) -> T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.take().unwrap() + } + + #[doc(hidden)] + pub unsafe fn from_handle(handle: u32) -> Self { + Self { + handle: _rt::Resource::from_handle(handle), + } + } + + #[doc(hidden)] + pub fn take_handle(&self) -> u32 { + _rt::Resource::take_handle(&self.handle) + } + + #[doc(hidden)] + pub fn handle(&self) -> u32 { + _rt::Resource::handle(&self.handle) + } + + // It's theoretically possible to implement the `GuestR` trait twice + // so guard against using it with two different types here. + #[doc(hidden)] + fn type_guard() { + use core::any::TypeId; + static mut LAST_TYPE: Option = None; + unsafe { + assert!(!cfg!(target_feature = "threads")); + let id = TypeId::of::(); + match LAST_TYPE { + Some(ty) => assert!(ty == id, "cannot use two types with this resource type"), + None => LAST_TYPE = Some(id), + } + } + } + + #[doc(hidden)] + pub unsafe fn dtor(handle: *mut u8) { + Self::type_guard::(); + let _ = _rt::Box::from_raw(handle as *mut _RRep); + } + + fn as_ptr(&self) -> *mut _RRep { + R::type_guard::(); + T::_resource_rep(self.handle()).cast() + } + } + + /// A borrowed version of [`R`] which represents a borrowed value + /// with the lifetime `'a`. + #[derive(Debug)] + #[repr(transparent)] + pub struct RBorrow<'a> { + rep: *mut u8, + _marker: core::marker::PhantomData<&'a R>, + } + + impl<'a> RBorrow<'a>{ + #[doc(hidden)] + pub unsafe fn lift(rep: usize) -> Self { + Self { + rep: rep as *mut u8, + _marker: core::marker::PhantomData, + } + } + + /// Gets access to the underlying `T` in this resource. + pub fn get(&self) -> &T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.as_ref().unwrap() + } + + // NB: mutable access is not allowed due to the component model allowing + // multiple borrows of the same resource. + + fn as_ptr(&self) -> *mut _RRep { + R::type_guard::(); + self.rep.cast() + } + } + + + unsafe impl _rt::WasmResource for R{ + #[inline] + unsafe fn drop(_handle: u32) { + { + #[link(wasm_import_module = "[export]foo:foo/resources")] + extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "[resource-drop]r")] + fn X5BexportX5DfooX3AfooX2FresourcesX00X5Bresource_dropX5Dr(_: u32); + } + + X5BexportX5DfooX3AfooX2FresourcesX00X5Bresource_dropX5Dr(_handle); + } + } + } + + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_constructor_r_cabi(arg0: i32,) -> i32 {#[cfg(target_arch="wasm32")] + _rt::run_ctors_once();let result0 = R::new(T::new(arg0 as u32)); + (result0).take_handle() as i32 + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_method_r_add_cabi(arg0: *mut u8,arg1: i32,) {#[cfg(target_arch="wasm32")] + _rt::run_ctors_once();T::add(RBorrow::lift(arg0 as usize).get(), arg1 as u32); + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_create_cabi() -> i32 {#[cfg(target_arch="wasm32")] + _rt::run_ctors_once();let result0 = T::create(); + (result0).take_handle() as i32 + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_borrows_cabi(arg0: *const u8,) {#[cfg(target_arch="wasm32")] + _rt::run_ctors_once();T::borrows(RBorrow::lift(arg0 as usize)); +} +#[doc(hidden)] +#[allow(non_snake_case)] +pub unsafe fn _export_consume_cabi(arg0: i32,) {#[cfg(target_arch="wasm32")] +_rt::run_ctors_once();T::consume(R::from_handle(arg0 as u32)); +} +pub trait Guest { + type R: GuestR; + fn create() -> R; + fn borrows(o: RBorrow<'_>,); + fn consume(o: R,); +} +pub trait GuestR: 'static { + + #[doc(hidden)] + unsafe fn _resource_new(val: *mut u8) -> u32 + where Self: Sized + { + #[link(wasm_import_module = "[export]foo:foo/resources")] + extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "[resource-new]r")] + fn X5BexportX5DfooX3AfooX2FresourcesX00X5Bresource_newX5Dr(_: *mut u8) -> u32; + } + X5BexportX5DfooX3AfooX2FresourcesX00X5Bresource_newX5Dr(val) + } + + #[doc(hidden)] + fn _resource_rep(handle: u32) -> *mut u8 + where Self: Sized + { + #[link(wasm_import_module = "[export]foo:foo/resources")] + extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "[resource-rep]r")] + fn X5BexportX5DfooX3AfooX2FresourcesX00X5Bresource_repX5Dr(_: u32) -> *mut u8; + } + unsafe { + X5BexportX5DfooX3AfooX2FresourcesX00X5Bresource_repX5Dr(handle) + } + } + + + fn new(a: u32,) -> Self; + fn add(&self,b: u32,); +} +#[doc(hidden)] + +macro_rules! __export_foo_foo_resources_cabi{ + ($ty:ident with_types_in $($path_to_types:tt)*) => (const _: () = { + + #[cfg_attr(target_arch = "wasm32", export_name = "foo:foo/resources#[constructor]r")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn fooX3AfooX2FresourcesX23X5BconstructorX5Dr(arg0: i32,) -> i32 { + $($path_to_types)*::_export_constructor_r_cabi::<<$ty as $($path_to_types)*::Guest>::R>(arg0) + } + #[cfg_attr(target_arch = "wasm32", export_name = "foo:foo/resources#[method]r.add")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn fooX3AfooX2FresourcesX23X5BmethodX5DrX2Eadd(arg0: *mut u8,arg1: i32,) { + $($path_to_types)*::_export_method_r_add_cabi::<<$ty as $($path_to_types)*::Guest>::R>(arg0, arg1) + } + #[cfg_attr(target_arch = "wasm32", export_name = "foo:foo/resources#create")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn fooX3AfooX2FresourcesX23create() -> i32 { + $($path_to_types)*::_export_create_cabi::<$ty>() + } + #[cfg_attr(target_arch = "wasm32", export_name = "foo:foo/resources#borrows")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn fooX3AfooX2FresourcesX23borrows(arg0: *const u8,) { + $($path_to_types)*::_export_borrows_cabi::<$ty>(arg0) + } + #[cfg_attr(target_arch = "wasm32", export_name = "foo:foo/resources#consume")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn fooX3AfooX2FresourcesX23consume(arg0: i32,) { + $($path_to_types)*::_export_consume_cabi::<$ty>(arg0) + } + + const _: () = { + #[doc(hidden)] + #[cfg_attr(target_arch = "wasm32", export_name = "foo:foo/resources#[dtor]r")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + #[allow(non_snake_case)] + unsafe extern "C" fn fooX3AfooX2FresourcesX23X5BdtorX5Dr(rep: *mut u8) { + $($path_to_types)*::R::dtor::< + <$ty as $($path_to_types)*::Guest>::R + >(rep) + } + }; + + };); +} +#[doc(hidden)] +pub(crate) use __export_foo_foo_resources_cabi; + +} + +} +} +} +mod _rt { + + + use core::fmt; + use core::marker; + use core::sync::atomic::{AtomicU32, Ordering::Relaxed}; + + /// A type which represents a component model resource, either imported or + /// exported into this component. + /// + /// This is a low-level wrapper which handles the lifetime of the resource + /// (namely this has a destructor). The `T` provided defines the component model + /// intrinsics that this wrapper uses. + /// + /// One of the chief purposes of this type is to provide `Deref` implementations + /// to access the underlying data when it is owned. + /// + /// This type is primarily used in generated code for exported and imported + /// resources. + #[repr(transparent)] + pub struct Resource { + // NB: This would ideally be `u32` but it is not. The fact that this has + // interior mutability is not exposed in the API of this type except for the + // `take_handle` method which is supposed to in theory be private. + // + // This represents, almost all the time, a valid handle value. When it's + // invalid it's stored as `u32::MAX`. + handle: AtomicU32, + _marker: marker::PhantomData, + } + + /// A trait which all wasm resources implement, namely providing the ability to + /// drop a resource. + /// + /// This generally is implemented by generated code, not user-facing code. + pub unsafe trait WasmResource { + /// Invokes the `[resource-drop]...` intrinsic. + unsafe fn drop(handle: u32); + } + + impl Resource { + #[doc(hidden)] + pub unsafe fn from_handle(handle: u32) -> Self { + debug_assert!(handle != u32::MAX); + Self { + handle: AtomicU32::new(handle), + _marker: marker::PhantomData, + } + } + + /// Takes ownership of the handle owned by `resource`. + /// + /// Note that this ideally would be `into_handle` taking `Resource` by + /// ownership. The code generator does not enable that in all situations, + /// unfortunately, so this is provided instead. + /// + /// Also note that `take_handle` is in theory only ever called on values + /// owned by a generated function. For example a generated function might + /// take `Resource` as an argument but then call `take_handle` on a + /// reference to that argument. In that sense the dynamic nature of + /// `take_handle` should only be exposed internally to generated code, not + /// to user code. + #[doc(hidden)] + pub fn take_handle(resource: &Resource) -> u32 { + resource.handle.swap(u32::MAX, Relaxed) + } + + #[doc(hidden)] + pub fn handle(resource: &Resource) -> u32 { + resource.handle.load(Relaxed) + } + } + + impl fmt::Debug for Resource { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Resource") + .field("handle", &self.handle) + .finish() + } + } + + impl Drop for Resource { + fn drop(&mut self) { + unsafe { + match self.handle.load(Relaxed) { + // If this handle was "taken" then don't do anything in the + // destructor. + u32::MAX => {} + + // ... but otherwise do actually destroy it with the imported + // component model intrinsic as defined through `T`. + other => T::drop(other), + } + } + } + } + + pub fn as_i32(t: T) -> i32 { + t.as_i32() + } + + pub trait AsI32 { + fn as_i32(self) -> i32; + } + + impl<'a, T: Copy + AsI32> AsI32 for &'a T { + fn as_i32(self) -> i32 { + (*self).as_i32() + } + } + + impl AsI32 for i32 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + + impl AsI32 for u32 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + + impl AsI32 for i16 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + + impl AsI32 for u16 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + + impl AsI32 for i8 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + + impl AsI32 for u8 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + + impl AsI32 for char { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + + impl AsI32 for usize { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + pub use alloc_crate::boxed::Box; + + #[cfg(target_arch = "wasm32")] + pub fn run_ctors_once() { + wit_bindgen::rt::run_ctors_once(); + } + extern crate alloc as alloc_crate; +} + +/// Generates `#[no_mangle]` functions to export the specified type as the +/// root implementation of all generated traits. +/// +/// For more information see the documentation of `wit_bindgen::generate!`. +/// +/// ```rust +/// # macro_rules! export{ ($($t:tt)*) => (); } +/// # trait Guest {} +/// struct MyType; +/// +/// impl Guest for MyType { +/// // ... +/// } +/// +/// export!(MyType); +/// ``` +#[allow(unused_macros)] +#[doc(hidden)] + +macro_rules! __export_the_world_impl { + ($ty:ident) => (self::export!($ty with_types_in self);); + ($ty:ident with_types_in $($path_to_types_root:tt)*) => ( + $($path_to_types_root)*::exports::foo::foo::resources::__export_foo_foo_resources_cabi!($ty with_types_in $($path_to_types_root)*::exports::foo::foo::resources); + ) +} +#[doc(inline)] +pub(crate) use __export_the_world_impl as export; + +#[cfg(target_arch = "wasm32")] +#[link_section = "component-type:wit-bindgen:0.24.0:the-world:encoded world"] +#[doc(hidden)] +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 460] = *b"\ +\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\xcc\x02\x01A\x02\x01\ +A\x04\x01B\x0d\x04\0\x01r\x03\x01\x01i\0\x01@\x01\x01ay\0\x01\x04\0\x0e[construc\ +tor]r\x01\x02\x01h\0\x01@\x02\x04self\x03\x01by\x01\0\x04\0\x0d[method]r.add\x01\ +\x04\x01@\0\0\x01\x04\0\x06create\x01\x05\x01@\x01\x01o\x03\x01\0\x04\0\x07borro\ +ws\x01\x06\x01@\x01\x01o\x01\x01\0\x04\0\x07consume\x01\x07\x03\x01\x11foo:foo/r\ +esources\x05\0\x01B\x0d\x04\0\x01r\x03\x01\x01i\0\x01@\x01\x01ay\0\x01\x04\0\x0e\ +[constructor]r\x01\x02\x01h\0\x01@\x02\x04self\x03\x01by\x01\0\x04\0\x0d[method]\ +r.add\x01\x04\x01@\0\0\x01\x04\0\x06create\x01\x05\x01@\x01\x01o\x03\x01\0\x04\0\ +\x07borrows\x01\x06\x01@\x01\x01o\x01\x01\0\x04\0\x07consume\x01\x07\x04\x01\x11\ +foo:foo/resources\x05\x01\x04\x01\x11foo:foo/the-world\x04\0\x0b\x0f\x01\0\x09th\ +e-world\x03\0\0\0G\x09producers\x01\x0cprocessed-by\x02\x0dwit-component\x070.20\ +2.0\x10wit-bindgen-rust\x060.24.0"; + +#[inline(never)] +#[doc(hidden)] +#[cfg(target_arch = "wasm32")] +pub fn __link_custom_section_describing_imports() { + wit_bindgen::rt::maybe_link_cabi_realloc(); +} + diff --git a/crates/cpp/tests/native_resources/the_world_cpp_native.h b/crates/cpp/tests/native_resources/the_world_cpp_native.h new file mode 100644 index 000000000..dbe1400c8 --- /dev/null +++ b/crates/cpp/tests/native_resources/the_world_cpp_native.h @@ -0,0 +1,46 @@ +// Generated by `wit-bindgen` 0.3.0. DO NOT EDIT! +#ifndef __CPP_NATIVE_BINDINGS_THE_WORLD_H +#define __CPP_NATIVE_BINDINGS_THE_WORLD_H +#define WIT_HOST_DIRECT +#include +#include +#include +#include +#include + +// guest imports (implemented here) +#include "foo-foo-resources-R.h" +namespace foo { +namespace foo { +namespace resources { +R::Owned Create(); +void Borrows(std::reference_wrapper o); +void Consume(R::Owned o); +// export_interface Interface(Id { idx: 0 }) +} // namespace resources +} // namespace foo +} // namespace foo +namespace exports { +namespace foo { +namespace foo { +namespace resources { +class R : public wit::ResourceExportBase { + +public: + ~R(); + R(uint32_t a); + void Add(uint32_t b) const; + R(wit::ResourceExportBase &&); + R(R &&) = default; + R &operator=(R &&) = default; +}; + +R Create(); +void Borrows(std::reference_wrapper o); +void Consume(R &&o); +} // namespace resources +} // namespace foo +} // namespace foo +} // namespace exports + +#endif diff --git a/crates/cpp/tests/native_resources/the_world_native.cpp b/crates/cpp/tests/native_resources/the_world_native.cpp new file mode 100644 index 000000000..eb9522ded --- /dev/null +++ b/crates/cpp/tests/native_resources/the_world_native.cpp @@ -0,0 +1,89 @@ +// Generated by `wit-bindgen` 0.3.0. DO NOT EDIT! +#include "the_world_cpp_native.h" +template std::map wit::ResourceTable::resources; +#include +extern "C" __attribute__((import_module("foo:foo/resources"))) +__attribute__((import_name("[dtor]r"))) void +fooX3AfooX2FresourcesX23X5BdtorX5Dr(uint8_t *); +extern "C" __attribute__((import_module("foo:foo/resources"))) +__attribute__((import_name("[constructor]r"))) +int32_t fooX3AfooX2FresourcesX23X5BconstructorX5Dr(int32_t); +extern "C" __attribute__((import_module("foo:foo/resources"))) +__attribute__((import_name("[method]r.add"))) void +fooX3AfooX2FresourcesX23X5BmethodX5DrX2Eadd(uint8_t *, int32_t); +extern "C" __attribute__((import_module("foo:foo/resources"))) +__attribute__((import_name("create"))) int32_t +fooX3AfooX2FresourcesX23create(); +extern "C" __attribute__((import_module("foo:foo/resources"))) +__attribute__((import_name("borrows"))) void + fooX3AfooX2FresourcesX23borrows(uint8_t*); +extern "C" __attribute__((import_module("foo:foo/resources"))) +__attribute__((import_name("consume"))) void + fooX3AfooX2FresourcesX23consume(int32_t); +extern "C" void fooX3AfooX2FresourcesX00X5Bresource_dropX5Dr(int32_t arg0) { + auto ptr = foo::foo::resources::R::remove_resource(arg0); + assert(ptr.has_value()); + foo::foo::resources::R::Dtor(*ptr); +} +extern "C" int32_t fooX3AfooX2FresourcesX00X5BconstructorX5Dr(int32_t arg0) { + auto result0 = foo::foo::resources::R::New((uint32_t(arg0))); + return result0.release()->get_handle(); +} +extern "C" void fooX3AfooX2FresourcesX00X5BmethodX5DrX2Eadd(int32_t arg0, + int32_t arg1) { + (**foo::foo::resources::R::lookup_resource(arg0)).Add((uint32_t(arg1))); +} +extern "C" int32_t fooX3AfooX2FresourcesX00create() { + auto result0 = foo::foo::resources::Create(); + return result0.release()->get_handle(); +} +extern "C" void fooX3AfooX2FresourcesX00borrows(int32_t arg0) { + foo::foo::resources::Borrows(**foo::foo::resources::R::lookup_resource(arg0)); +} +extern "C" void fooX3AfooX2FresourcesX00consume(int32_t arg0) { + auto obj0 = foo::foo::resources::R::lookup_resource(arg0); + //assert(obj0 != nullptr); + foo::foo::resources::Consume(foo::foo::resources::R::Owned(*obj0)); +} +exports::foo::foo::resources::R::~R() { + if (this->rep) { + fooX3AfooX2FresourcesX23X5BdtorX5Dr(this->rep); + } +} +exports::foo::foo::resources::R::R(uint32_t a) { + auto ret = fooX3AfooX2FresourcesX23X5BconstructorX5Dr((int32_t(a))); + wit::ResourceExportBase retobj = wit::ResourceExportBase{ret}; + this->index = retobj.get_handle(); + this->rep = retobj.take_rep(); +} +void exports::foo::foo::resources::R::Add(uint32_t b) const { + fooX3AfooX2FresourcesX23X5BmethodX5DrX2Eadd((*this).get_rep(), (int32_t(b))); +} +exports::foo::foo::resources::R::R(wit::ResourceExportBase &&b) + : wit::ResourceExportBase(std::move(b)) {} +extern "C" int32_t +X5BexportX5DfooX3AfooX2FresourcesX00X5Bresource_newX5Dr(uint8_t *arg0) { + return exports::foo::foo::resources::R::store_resource(std::move(arg0)); +} +extern "C" uint8_t * +X5BexportX5DfooX3AfooX2FresourcesX00X5Bresource_repX5Dr(int32_t arg0) { + return *exports::foo::foo::resources::R::lookup_resource(arg0); +} +extern "C" void +X5BexportX5DfooX3AfooX2FresourcesX00X5Bresource_dropX5Dr(int32_t arg0) { + auto obj = exports::foo::foo::resources::R::remove_resource(arg0); + fooX3AfooX2FresourcesX23X5BdtorX5Dr(*obj); +} +exports::foo::foo::resources::R exports::foo::foo::resources::Create() { + auto ret = fooX3AfooX2FresourcesX23create(); + return wit::ResourceExportBase{ret}; +} +void exports::foo::foo::resources::Borrows(std::reference_wrapper o) { + fooX3AfooX2FresourcesX23borrows(o.get().get_rep()); +} +void exports::foo::foo::resources::Consume(R &&o) { + auto rep0 = o.take_rep(); + fooX3AfooX2FresourcesX23consume(o.get_handle()); +} + +// Component Adapters diff --git a/crates/cpp/tests/native_resources/wit/resources_simple.wit b/crates/cpp/tests/native_resources/wit/resources_simple.wit new file mode 100644 index 000000000..c849359af --- /dev/null +++ b/crates/cpp/tests/native_resources/wit/resources_simple.wit @@ -0,0 +1,16 @@ +package foo:foo; + +interface resources { + resource r { + constructor(a: u32); + add: func(b: u32); + } + create: func() -> r; + borrows: func(o: borrow); + consume: func(o: r); +} + +world the-world { + import resources; + export resources; +} diff --git a/crates/cpp/tests/native_strings/.gitignore b/crates/cpp/tests/native_strings/.gitignore new file mode 100644 index 000000000..56eab019c --- /dev/null +++ b/crates/cpp/tests/native_strings/.gitignore @@ -0,0 +1,3 @@ +/*.o +/libstrings.so +/app-strings diff --git a/crates/cpp/tests/native_strings/Makefile b/crates/cpp/tests/native_strings/Makefile new file mode 100644 index 000000000..54cdcdf09 --- /dev/null +++ b/crates/cpp/tests/native_strings/Makefile @@ -0,0 +1,36 @@ +CXXFLAGS=-g -O0 -I../../helper-types +WIT_BINDGEN=../../../../target/debug/wit-bindgen + +all: libstrings.so app-strings + +libstrings.so: the_world.pie.o guest.pie.o + $(CXX) $(CXXFLAGS) -shared -o $@ $^ -Wl,--version-script=guest.lds + +%.pie.o: %.cpp + $(CXX) $(CXXFLAGS) -fPIE -o $@ -c $^ + +app-strings: the_world_native.o main.o + $(CXX) $(CXXFLAGS) -o $@ $^ -L. -lstrings + +bindgen: wit/strings.wit + $(WIT_BINDGEN) cpp wit --wasm64 --format + $(WIT_BINDGEN) cpp wit --wasm64 --format --direct + cd rust/src ; ../../$(WIT_BINDGEN) rust ../../wit --wasm64 + +guest.wasm: the_world.cpp guest.cpp + /opt/wasi-sdk/bin/clang++ -o $@ $^ $(CXXFLAGS) + +guest_release.wasm: the_world.cpp guest.cpp + /opt/wasi-sdk/bin/clang++ -o $@ $^ $(CXXFLAGS) -g0 -O3 + +clean: + -rm *.o libstrings.so app-strings + +run: + LD_LIBRARY_PATH=. ./app-strings + +valgrind: + LD_LIBRARY_PATH=. valgrind ./app-strings + +w2c2_guest.c: guest_release.wasm + w2c2 $^ $@ diff --git a/crates/cpp/tests/native_strings/guest.cpp b/crates/cpp/tests/native_strings/guest.cpp new file mode 100644 index 000000000..90fa6258d --- /dev/null +++ b/crates/cpp/tests/native_strings/guest.cpp @@ -0,0 +1,13 @@ +#include "the_world_cpp.h" + +void exports::foo::foo::strings::A(wit::string &&x) { + ::foo::foo::strings::A(x.get_view()); +} + +wit::string exports::foo::foo::strings::B() { + return ::foo::foo::strings::B(); +} + +wit::string exports::foo::foo::strings::C(wit::string &&x, wit::string &&b) { + return ::foo::foo::strings::C(x.get_view(), b.get_view()); +} diff --git a/crates/cpp/tests/native_strings/guest.lds b/crates/cpp/tests/native_strings/guest.lds new file mode 100644 index 000000000..da4b4f12e --- /dev/null +++ b/crates/cpp/tests/native_strings/guest.lds @@ -0,0 +1,7 @@ +{ + global: + fooX3AfooX2FstringsX23*; + cabi_post_fooX3AfooX2FstringsX23*; + cabi_realloc; + local: *; +}; diff --git a/crates/cpp/tests/native_strings/main.cpp b/crates/cpp/tests/native_strings/main.cpp new file mode 100644 index 000000000..0f86198e5 --- /dev/null +++ b/crates/cpp/tests/native_strings/main.cpp @@ -0,0 +1,33 @@ + +#include "the_world_cpp_native.h" +#include + +void foo::foo::strings::A(std::string_view x) { + std::cout << x << std::endl; +} +wit::string foo::foo::strings::B() { + wit::string b = wit::string::from_view(std::string_view("hello B")); + return b; +} +wit::string foo::foo::strings::C(std::string_view a, std::string_view b) { + std::cout << a << '|' << b << std::endl; + wit::string c = wit::string::from_view(std::string_view("hello C")); + return c; +} + +int main() { + wit::string a = wit::string::from_view(std::string_view("hello A")); + exports::foo::foo::strings::A(a); + + { + auto b = exports::foo::foo::strings::B(); + std::cout << b.inner() << std::endl; + // make sure that b's result is destructed before calling C + } + + wit::string c1 = wit::string::from_view(std::string_view("hello C1")); + wit::string c2 = wit::string::from_view(std::string_view("hello C2")); + auto c = exports::foo::foo::strings::C(c1, c2); + std::cout << c.inner() << std::endl; + return 0; +} diff --git a/crates/cpp/tests/native_strings/rust/Cargo.lock b/crates/cpp/tests/native_strings/rust/Cargo.lock new file mode 100644 index 000000000..d85cfd92e --- /dev/null +++ b/crates/cpp/tests/native_strings/rust/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "strings" +version = "0.1.0" diff --git a/crates/cpp/tests/native_strings/rust/Cargo.toml b/crates/cpp/tests/native_strings/rust/Cargo.toml new file mode 100644 index 000000000..54920869d --- /dev/null +++ b/crates/cpp/tests/native_strings/rust/Cargo.toml @@ -0,0 +1,12 @@ +[workspace] + +[package] +name = "strings" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +# wit-bindgen-rt = "0.21.0" diff --git a/crates/cpp/tests/native_strings/rust/src/lib.rs b/crates/cpp/tests/native_strings/rust/src/lib.rs new file mode 100644 index 000000000..0fb041a18 --- /dev/null +++ b/crates/cpp/tests/native_strings/rust/src/lib.rs @@ -0,0 +1,49 @@ +use std::alloc::Layout; + +use the_world::exports::foo::foo::strings::Guest; + +mod the_world; + +struct MyWorld; + +impl Guest for MyWorld { + fn a(x: String,) { + the_world::foo::foo::strings::a(&x); + } + + fn b() -> String { + the_world::foo::foo::strings::b() + } + + fn c(a: String,b: String,) -> String { + the_world::foo::foo::strings::c(&a, &b) + } +} + +the_world::export!(MyWorld with_types_in the_world); + +// the crate wit-bindgen-rt doesn't work on native +#[no_mangle] +pub unsafe extern "C" fn cabi_realloc( + old_ptr: *mut u8, + old_len: usize, + align: usize, + new_len: usize, +) -> *mut u8 { + let layout; + let ptr = if old_len == 0 { + if new_len == 0 { + return align as *mut u8; + } + layout = Layout::from_size_align_unchecked(new_len, align); + std::alloc::alloc(layout) + } else { + debug_assert_ne!(new_len, 0, "non-zero old_len requires non-zero new_len!"); + layout = Layout::from_size_align_unchecked(old_len, align); + std::alloc::realloc(old_ptr, layout, new_len) + }; + if ptr.is_null() { + unreachable!(); + } + return ptr; +} diff --git a/crates/cpp/tests/native_strings/rust/src/the_world.rs b/crates/cpp/tests/native_strings/rust/src/the_world.rs new file mode 100644 index 000000000..18fde4703 --- /dev/null +++ b/crates/cpp/tests/native_strings/rust/src/the_world.rs @@ -0,0 +1,266 @@ +// Generated by `wit-bindgen` 0.28.0. DO NOT EDIT! +// Options used: +#[allow(dead_code)] +pub mod foo { + #[allow(dead_code)] + pub mod foo { + #[allow(dead_code, clippy::all)] + pub mod strings { + #[used] + #[doc(hidden)] + #[cfg(target_arch = "wasm32")] + static __FORCE_SECTION_REF: fn() = super::super::super::__link_custom_section_describing_imports; + use super::super::super::_rt; + #[allow(unused_unsafe, clippy::all)] + pub fn a(x: &str,) -> (){ + unsafe { + let vec0 = x; + let ptr0 = vec0.as_ptr().cast::(); + let len0 = vec0.len(); + + #[link(wasm_import_module = "foo:foo/strings")] + extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "a")] + fn fooX3AfooX2FstringsX00a(_: *mut u8, _: usize, ); + } + fooX3AfooX2FstringsX00a(ptr0.cast_mut(), len0); + } + } + #[allow(unused_unsafe, clippy::all)] + pub fn b() -> _rt::String{ + unsafe { + #[repr(align(8))] + struct RetArea([::core::mem::MaybeUninit::; 16]); + let mut ret_area = RetArea([::core::mem::MaybeUninit::uninit(); 16]); + let ptr0 = ret_area.0.as_mut_ptr().cast::(); + #[link(wasm_import_module = "foo:foo/strings")] + extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "b")] + fn fooX3AfooX2FstringsX00b(_: *mut u8, ); + } + fooX3AfooX2FstringsX00b(ptr0); + let l1 = *ptr0.add(0).cast::<*mut u8>(); + let l2 = *ptr0.add(8).cast::(); + let len3 = l2; + let bytes3 = _rt::Vec::from_raw_parts(l1.cast(), len3, len3); + let result4 = _rt::string_lift(bytes3); + result4 + } + } + #[allow(unused_unsafe, clippy::all)] + pub fn c(a: &str,b: &str,) -> _rt::String{ + unsafe { + #[repr(align(8))] + struct RetArea([::core::mem::MaybeUninit::; 16]); + let mut ret_area = RetArea([::core::mem::MaybeUninit::uninit(); 16]); + let vec0 = a; + let ptr0 = vec0.as_ptr().cast::(); + let len0 = vec0.len(); + let vec1 = b; + let ptr1 = vec1.as_ptr().cast::(); + let len1 = vec1.len(); + let ptr2 = ret_area.0.as_mut_ptr().cast::(); + #[link(wasm_import_module = "foo:foo/strings")] + extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "c")] + fn fooX3AfooX2FstringsX00c(_: *mut u8, _: usize, _: *mut u8, _: usize, _: *mut u8, ); + } + fooX3AfooX2FstringsX00c(ptr0.cast_mut(), len0, ptr1.cast_mut(), len1, ptr2); + let l3 = *ptr2.add(0).cast::<*mut u8>(); + let l4 = *ptr2.add(8).cast::(); + let len5 = l4; + let bytes5 = _rt::Vec::from_raw_parts(l3.cast(), len5, len5); + let result6 = _rt::string_lift(bytes5); + result6 + } + } + + } + + } +} +#[allow(dead_code)] +pub mod exports { + #[allow(dead_code)] + pub mod foo { + #[allow(dead_code)] + pub mod foo { + #[allow(dead_code, clippy::all)] + pub mod strings { + #[used] + #[doc(hidden)] + #[cfg(target_arch = "wasm32")] + static __FORCE_SECTION_REF: fn() = super::super::super::super::__link_custom_section_describing_imports; + use super::super::super::super::_rt; + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_a_cabi(arg0: *mut u8,arg1: usize,) {#[cfg(target_arch="wasm32")] + _rt::run_ctors_once();let len0 = arg1; + let bytes0 = _rt::Vec::from_raw_parts(arg0.cast(), len0, len0); + T::a(_rt::string_lift(bytes0)); + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_b_cabi() -> *mut u8 {#[cfg(target_arch="wasm32")] + _rt::run_ctors_once();let result0 = T::b(); + let ptr1 = _RET_AREA.0.as_mut_ptr().cast::(); + let vec2 = (result0.into_bytes()).into_boxed_slice(); + let ptr2 = vec2.as_ptr().cast::(); + let len2 = vec2.len(); + ::core::mem::forget(vec2); + *ptr1.add(8).cast::() = len2; + *ptr1.add(0).cast::<*mut u8>() = ptr2.cast_mut(); + ptr1 + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn __post_return_b(arg0: *mut u8,) { + let l0 = *arg0.add(0).cast::<*mut u8>(); + let l1 = *arg0.add(8).cast::(); + _rt::cabi_dealloc(l0, l1, 1); + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_c_cabi(arg0: *mut u8,arg1: usize,arg2: *mut u8,arg3: usize,) -> *mut u8 {#[cfg(target_arch="wasm32")] + _rt::run_ctors_once();let len0 = arg1; + let bytes0 = _rt::Vec::from_raw_parts(arg0.cast(), len0, len0); + let len1 = arg3; + let bytes1 = _rt::Vec::from_raw_parts(arg2.cast(), len1, len1); + let result2 = T::c(_rt::string_lift(bytes0), _rt::string_lift(bytes1)); + let ptr3 = _RET_AREA.0.as_mut_ptr().cast::(); + let vec4 = (result2.into_bytes()).into_boxed_slice(); + let ptr4 = vec4.as_ptr().cast::(); + let len4 = vec4.len(); + ::core::mem::forget(vec4); + *ptr3.add(8).cast::() = len4; + *ptr3.add(0).cast::<*mut u8>() = ptr4.cast_mut(); + ptr3 + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn __post_return_c(arg0: *mut u8,) { + let l0 = *arg0.add(0).cast::<*mut u8>(); + let l1 = *arg0.add(8).cast::(); + _rt::cabi_dealloc(l0, l1, 1); + } + pub trait Guest { + fn a(x: _rt::String,) -> (); + fn b() -> _rt::String; + fn c(a: _rt::String,b: _rt::String,) -> _rt::String; + } + #[doc(hidden)] + + macro_rules! __export_foo_foo_strings_cabi{ + ($ty:ident with_types_in $($path_to_types:tt)*) => (const _: () = { + + #[cfg_attr(target_arch = "wasm32", export_name = "foo:foo/strings#a")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn fooX3AfooX2FstringsX23a(arg0: *mut u8,arg1: usize,) { + $($path_to_types)*::_export_a_cabi::<$ty>(arg0, arg1) + } + #[cfg_attr(target_arch = "wasm32", export_name = "foo:foo/strings#b")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn fooX3AfooX2FstringsX23b() -> *mut u8 { + $($path_to_types)*::_export_b_cabi::<$ty>() + } + #[cfg_attr(target_arch = "wasm32", export_name = "cabi_post_foo:foo/strings#b")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn cabi_post_fooX3AfooX2FstringsX23b(arg0: *mut u8,) { + $($path_to_types)*::__post_return_b::<$ty>(arg0) + } + #[cfg_attr(target_arch = "wasm32", export_name = "foo:foo/strings#c")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn fooX3AfooX2FstringsX23c(arg0: *mut u8,arg1: usize,arg2: *mut u8,arg3: usize,) -> *mut u8 { + $($path_to_types)*::_export_c_cabi::<$ty>(arg0, arg1, arg2, arg3) + } + #[cfg_attr(target_arch = "wasm32", export_name = "cabi_post_foo:foo/strings#c")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn cabi_post_fooX3AfooX2FstringsX23c(arg0: *mut u8,) { + $($path_to_types)*::__post_return_c::<$ty>(arg0) + } + };); + } + #[doc(hidden)] + pub(crate) use __export_foo_foo_strings_cabi; + #[repr(align(8))] + struct _RetArea([::core::mem::MaybeUninit::; 16]); + static mut _RET_AREA: _RetArea = _RetArea([::core::mem::MaybeUninit::uninit(); 16]); + +} + +} +} +} +mod _rt { + pub use alloc_crate::string::String; + pub use alloc_crate::vec::Vec; + pub unsafe fn string_lift(bytes: Vec) -> String { + if cfg!(debug_assertions) { + String::from_utf8(bytes).unwrap() + } else { + String::from_utf8_unchecked(bytes) + } + } + + #[cfg(target_arch = "wasm32")] + pub fn run_ctors_once() { + wit_bindgen::rt::run_ctors_once(); + } + pub unsafe fn cabi_dealloc(ptr: *mut u8, size: usize, align: usize) { + if size == 0 { + return; + } + let layout = alloc::Layout::from_size_align_unchecked(size, align); + alloc::dealloc(ptr, layout); + } + extern crate alloc as alloc_crate; + pub use alloc_crate::alloc; +} + +/// Generates `#[no_mangle]` functions to export the specified type as the +/// root implementation of all generated traits. +/// +/// For more information see the documentation of `wit_bindgen::generate!`. +/// +/// ```rust +/// # macro_rules! export{ ($($t:tt)*) => (); } +/// # trait Guest {} +/// struct MyType; +/// +/// impl Guest for MyType { +/// // ... +/// } +/// +/// export!(MyType); +/// ``` +#[allow(unused_macros)] +#[doc(hidden)] + +macro_rules! __export_the_world_impl { + ($ty:ident) => (self::export!($ty with_types_in self);); + ($ty:ident with_types_in $($path_to_types_root:tt)*) => ( + $($path_to_types_root)*::exports::foo::foo::strings::__export_foo_foo_strings_cabi!($ty with_types_in $($path_to_types_root)*::exports::foo::foo::strings); + ) +} +#[doc(inline)] +pub(crate) use __export_the_world_impl as export; + +#[cfg(target_arch = "wasm32")] +#[link_section = "component-type:wit-bindgen:0.28.0:the-world:encoded world"] +#[doc(hidden)] +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 286] = *b"\ +\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\x9e\x01\x01A\x02\x01\ +A\x04\x01B\x06\x01@\x01\x01xs\x01\0\x04\0\x01a\x01\0\x01@\0\0s\x04\0\x01b\x01\x01\ +\x01@\x02\x01as\x01bs\0s\x04\0\x01c\x01\x02\x03\x01\x0ffoo:foo/strings\x05\0\x01\ +B\x06\x01@\x01\x01xs\x01\0\x04\0\x01a\x01\0\x01@\0\0s\x04\0\x01b\x01\x01\x01@\x02\ +\x01as\x01bs\0s\x04\0\x01c\x01\x02\x04\x01\x0ffoo:foo/strings\x05\x01\x04\x01\x11\ +foo:foo/the-world\x04\0\x0b\x0f\x01\0\x09the-world\x03\0\0\0G\x09producers\x01\x0c\ +processed-by\x02\x0dwit-component\x070.214.0\x10wit-bindgen-rust\x060.28.0"; + +#[inline(never)] +#[doc(hidden)] +#[cfg(target_arch = "wasm32")] +pub fn __link_custom_section_describing_imports() { + wit_bindgen::rt::maybe_link_cabi_realloc(); +} + diff --git a/crates/cpp/tests/native_strings/the_world.cpp b/crates/cpp/tests/native_strings/the_world.cpp new file mode 100644 index 000000000..03c455465 --- /dev/null +++ b/crates/cpp/tests/native_strings/the_world.cpp @@ -0,0 +1,125 @@ +// Generated by `wit-bindgen` 0.3.0. DO NOT EDIT! + +// Ensure that the *_component_type.o object is linked in +#ifdef __wasm32__ +extern void __component_type_object_force_link_the_world(void); +void __component_type_object_force_link_the_world_public_use_in_this_compilation_unit( + void) { + __component_type_object_force_link_the_world(); +} +#endif +#include "the_world_cpp.h" +#include // realloc + +extern "C" void *cabi_realloc(void *ptr, size_t old_size, size_t align, + size_t new_size); + +__attribute__((__weak__, __export_name__("cabi_realloc"))) void * +cabi_realloc(void *ptr, size_t old_size, size_t align, size_t new_size) { + (void)old_size; + if (new_size == 0) + return (void *)align; + void *ret = realloc(ptr, new_size); + if (!ret) + abort(); + return ret; +} + +extern "C" __attribute__((import_module("foo:foo/strings"))) +__attribute__((import_name("a"))) void +fooX3AfooX2FstringsX00a(uint8_t *, size_t); +extern "C" __attribute__((import_module("foo:foo/strings"))) +__attribute__((import_name("b"))) void +fooX3AfooX2FstringsX00b(uint8_t *); +extern "C" __attribute__((import_module("foo:foo/strings"))) +__attribute__((import_name("c"))) void +fooX3AfooX2FstringsX00c(uint8_t *, size_t, uint8_t *, size_t, uint8_t *); +void foo::foo::strings::A(std::string_view x) { + auto const &vec0 = x; + auto ptr0 = (uint8_t *)(vec0.data()); + auto len0 = (size_t)(vec0.size()); + fooX3AfooX2FstringsX00a(ptr0, len0); +} +wit::string foo::foo::strings::B() { + uint64_t ret_area[2]; + uint8_t *ptr0 = (uint8_t *)(&ret_area); + fooX3AfooX2FstringsX00b(ptr0); + auto len1 = *((size_t *)(ptr0 + 8)); + + auto result2 = wit::string((char const *)(*((uint8_t **)(ptr0 + 0))), len1); + return result2; +} +wit::string foo::foo::strings::C(std::string_view a, std::string_view b) { + auto const &vec0 = a; + auto ptr0 = (uint8_t *)(vec0.data()); + auto len0 = (size_t)(vec0.size()); + auto const &vec1 = b; + auto ptr1 = (uint8_t *)(vec1.data()); + auto len1 = (size_t)(vec1.size()); + uint64_t ret_area[2]; + uint8_t *ptr2 = (uint8_t *)(&ret_area); + fooX3AfooX2FstringsX00c(ptr0, len0, ptr1, len1, ptr2); + auto len3 = *((size_t *)(ptr2 + 8)); + + auto result4 = wit::string((char const *)(*((uint8_t **)(ptr2 + 0))), len3); + return result4; +} +extern "C" __attribute__((__export_name__("foo:foo/strings#a"))) void +fooX3AfooX2FstringsX23a(uint8_t *arg0, size_t arg1) { + auto len0 = arg1; + + exports::foo::foo::strings::A(wit::string((char const *)(arg0), len0)); +} +extern "C" __attribute__((__export_name__("foo:foo/strings#b"))) uint8_t * +fooX3AfooX2FstringsX23b() { + auto result0 = exports::foo::foo::strings::B(); + static uint64_t ret_area[2]; + uint8_t *ptr1 = (uint8_t *)(&ret_area); + auto const &vec2 = result0; + auto ptr2 = (uint8_t *)(vec2.data()); + auto len2 = (size_t)(vec2.size()); + result0.leak(); + + *((size_t *)(ptr1 + 8)) = len2; + *((uint8_t **)(ptr1 + 0)) = ptr2; + return ptr1; +} +extern "C" + __attribute__((__weak__, + __export_name__("cabi_post_fooX3AfooX2FstringsX23b"))) void + cabi_post_fooX3AfooX2FstringsX23b(uint8_t *arg0) { + if ((*((size_t *)(arg0 + 8))) > 0) { + wit::string::drop_raw((void *)(*((uint8_t **)(arg0 + 0)))); + } +} +extern "C" __attribute__((__export_name__("foo:foo/strings#c"))) uint8_t * +fooX3AfooX2FstringsX23c(uint8_t *arg0, size_t arg1, uint8_t *arg2, + size_t arg3) { + auto len0 = arg1; + + auto len1 = arg3; + + auto result2 = + exports::foo::foo::strings::C(wit::string((char const *)(arg0), len0), + wit::string((char const *)(arg2), len1)); + static uint64_t ret_area[2]; + uint8_t *ptr3 = (uint8_t *)(&ret_area); + auto const &vec4 = result2; + auto ptr4 = (uint8_t *)(vec4.data()); + auto len4 = (size_t)(vec4.size()); + result2.leak(); + + *((size_t *)(ptr3 + 8)) = len4; + *((uint8_t **)(ptr3 + 0)) = ptr4; + return ptr3; +} +extern "C" + __attribute__((__weak__, + __export_name__("cabi_post_fooX3AfooX2FstringsX23c"))) void + cabi_post_fooX3AfooX2FstringsX23c(uint8_t *arg0) { + if ((*((size_t *)(arg0 + 8))) > 0) { + wit::string::drop_raw((void *)(*((uint8_t **)(arg0 + 0)))); + } +} + +// Component Adapters diff --git a/crates/cpp/tests/native_strings/the_world_cpp.h b/crates/cpp/tests/native_strings/the_world_cpp.h new file mode 100644 index 000000000..41957c9c7 --- /dev/null +++ b/crates/cpp/tests/native_strings/the_world_cpp.h @@ -0,0 +1,30 @@ +// Generated by `wit-bindgen` 0.3.0. DO NOT EDIT! +#ifndef __CPP_GUEST_BINDINGS_THE_WORLD_H +#define __CPP_GUEST_BINDINGS_THE_WORLD_H +#include +#include +#include +#include +namespace foo { +namespace foo { +namespace strings { +void A(std::string_view x); +wit::string B(); +wit::string C(std::string_view a, std::string_view b); +// export_interface Interface(Id { idx: 0 }) +} // namespace strings +} // namespace foo +} // namespace foo +namespace exports { +namespace foo { +namespace foo { +namespace strings { +void A(wit::string &&x); +wit::string B(); +wit::string C(wit::string &&a, wit::string &&b); +} // namespace strings +} // namespace foo +} // namespace foo +} // namespace exports + +#endif diff --git a/crates/cpp/tests/native_strings/the_world_cpp_native.h b/crates/cpp/tests/native_strings/the_world_cpp_native.h new file mode 100644 index 000000000..18546c4cb --- /dev/null +++ b/crates/cpp/tests/native_strings/the_world_cpp_native.h @@ -0,0 +1,31 @@ +// Generated by `wit-bindgen` 0.3.0. DO NOT EDIT! +#ifndef __CPP_NATIVE_BINDINGS_THE_WORLD_H +#define __CPP_NATIVE_BINDINGS_THE_WORLD_H +#define WIT_HOST_DIRECT +#include +#include +#include +#include +namespace foo { +namespace foo { +namespace strings { +void A(std::string_view x); +wit::string B(); +wit::string C(std::string_view a, std::string_view b); +// export_interface Interface(Id { idx: 0 }) +} // namespace strings +} // namespace foo +} // namespace foo +namespace exports { +namespace foo { +namespace foo { +namespace strings { +void A(wit::string x); +wit::guest_owned B(); +wit::guest_owned C(wit::string a, wit::string b); +} // namespace strings +} // namespace foo +} // namespace foo +} // namespace exports + +#endif diff --git a/crates/cpp/tests/native_strings/the_world_native.cpp b/crates/cpp/tests/native_strings/the_world_native.cpp new file mode 100644 index 000000000..9e5ad10ca --- /dev/null +++ b/crates/cpp/tests/native_strings/the_world_native.cpp @@ -0,0 +1,79 @@ +// Generated by `wit-bindgen` 0.3.0. DO NOT EDIT! +#include "the_world_cpp_native.h" +extern "C" __attribute__((import_module("foo:foo/strings"))) +__attribute__((import_name("a"))) void +fooX3AfooX2FstringsX23a(uint8_t *, size_t); +extern "C" __attribute__((import_module("foo:foo/strings"))) +__attribute__((import_name("b"))) uint8_t * +fooX3AfooX2FstringsX23b(); +extern "C" __attribute__((import_module("cabi_post_foo:foo/strings"))) +__attribute__((import_name("b"))) void +cabi_post_fooX3AfooX2FstringsX23b(uint8_t *); +extern "C" __attribute__((import_module("foo:foo/strings"))) +__attribute__((import_name("c"))) uint8_t * +fooX3AfooX2FstringsX23c(uint8_t *, size_t, uint8_t *, size_t); +extern "C" __attribute__((import_module("cabi_post_foo:foo/strings"))) +__attribute__((import_name("c"))) void +cabi_post_fooX3AfooX2FstringsX23c(uint8_t *); +extern "C" void fooX3AfooX2FstringsX00a(uint8_t *arg0, size_t arg1) { + auto len0 = arg1; + + foo::foo::strings::A(std::string_view((char const *)(arg0), len0)); +} +extern "C" void fooX3AfooX2FstringsX00b(uint8_t *arg0, uint8_t *resultptr) { + auto result0 = foo::foo::strings::B(); + auto const &vec1 = result0; + auto ptr1 = vec1.data(); + auto len1 = vec1.size(); + *((size_t *)(arg0 + 8)) = len1; + *((uint8_t **)(arg0 + 0)) = ptr1; +} +extern "C" void fooX3AfooX2FstringsX00c(uint8_t *arg0, size_t arg1, + uint8_t *arg2, size_t arg3, + uint8_t *arg4, uint8_t *resultptr) { + auto len0 = arg1; + + auto len1 = arg3; + + auto result2 = + foo::foo::strings::C(std::string_view((char const *)(arg0), len0), + std::string_view((char const *)(arg2), len1)); + auto const &vec3 = result2; + auto ptr3 = vec3.data(); + auto len3 = vec3.size(); + *((size_t *)(arg4 + 8)) = len3; + *((uint8_t **)(arg4 + 0)) = ptr3; +} +void exports::foo::foo::strings::A(wit::string x) { + auto const &vec0 = x; + auto ptr0 = vec0.data(); + auto len0 = vec0.size(); + fooX3AfooX2FstringsX23a(ptr0, len0); +} +wit::guest_owned exports::foo::foo::strings::B() { + auto ret = fooX3AfooX2FstringsX23b(); + auto len0 = *((size_t *)(ret + 8)); + + auto result1 = + std::string_view((char const *)(*((uint8_t **)(ret + 0))), len0); + return wit::guest_owned(result1, ret, + cabi_post_fooX3AfooX2FstringsX23b); +} +wit::guest_owned +exports::foo::foo::strings::C(wit::string a, wit::string b) { + auto const &vec0 = a; + auto ptr0 = vec0.data(); + auto len0 = vec0.size(); + auto const &vec1 = b; + auto ptr1 = vec1.data(); + auto len1 = vec1.size(); + auto ret = fooX3AfooX2FstringsX23c(ptr0, len0, ptr1, len1); + auto len2 = *((size_t *)(ret + 8)); + + auto result3 = + std::string_view((char const *)(*((uint8_t **)(ret + 0))), len2); + return wit::guest_owned(result3, ret, + cabi_post_fooX3AfooX2FstringsX23c); +} + +// Component Adapters diff --git a/crates/cpp/tests/native_strings/w2c2/.gitignore b/crates/cpp/tests/native_strings/w2c2/.gitignore new file mode 100644 index 000000000..f1cad9d4d --- /dev/null +++ b/crates/cpp/tests/native_strings/w2c2/.gitignore @@ -0,0 +1,5 @@ +/libstrings.so +/*.o +/w2c2_base.h +/w2c2_guest.? +/the-world_bridge.c diff --git a/crates/cpp/tests/native_strings/w2c2/Makefile b/crates/cpp/tests/native_strings/w2c2/Makefile new file mode 100644 index 000000000..423211f7d --- /dev/null +++ b/crates/cpp/tests/native_strings/w2c2/Makefile @@ -0,0 +1,36 @@ +CXXFLAGS=-g -O0 -I../../../helper-types +WIT_BINDGEN=../../../../../target/debug/wit-bindgen +W2C2_PATH=$(HOME)/github/w2c2 + +all: libstrings.so + +w2c2_base.h: $(W2C2_PATH)/w2c2/w2c2_base.h + ln -s $^ . + +%.pie.o: %.c + $(CC) $(CXXFLAGS) -fPIE -o $@ -c $^ + +libstrings.so: w2c2_guest.pie.o the-world_bridge.pie.o wasi_dummy.pie.o + $(CC) $(CXXFLAGS) -shared -o $@ $^ -Wl,--version-script=../guest.lds + +guest_release.wasm: ../the_world.cpp ../guest.cpp + /opt/wasi-sdk/bin/clang++ -o $@ $^ $(CXXFLAGS) -g0 -O3 + +clean: + -rm *.o libstrings.so + +run: + LD_LIBRARY_PATH=. ../app-strings + +valgrind: + LD_LIBRARY_PATH=. valgrind ../app-strings + +w2c2_guest.c: guest_release.wasm + $(W2C2_PATH)/build/w2c2/w2c2 $^ $@ + +the-world_bridge.c: the-world_bridge_target.c + cp $^ $@ + +# not yet up to the task +#the-world_bridge.c: $(WIT_BINDGEN) +# $(WIT_BINDGEN) bridge ../wit --instance guestrelease --include w2c2_guest.h diff --git a/crates/cpp/tests/native_strings/w2c2/the-world_bridge_target.c b/crates/cpp/tests/native_strings/w2c2/the-world_bridge_target.c new file mode 100644 index 000000000..3c2d967f5 --- /dev/null +++ b/crates/cpp/tests/native_strings/w2c2/the-world_bridge_target.c @@ -0,0 +1,98 @@ + +#include +#include +#include "w2c2_guest.h" + +static guestreleaseInstance* instance; +static guestreleaseInstance app_instance; + +void trap(Trap trap) { + abort(); +} + +guestreleaseInstance* get_app() { + if (!instance) { + guestreleaseInstantiate(&app_instance, NULL); + instance = &app_instance; + } + return instance; +} + +__attribute__ ((visibility ("default"))) +void *cabi_realloc(void *ptr, size_t old_size, size_t align, + size_t new_size) { + uint8_t *linmem = guestrelease_memory(get_app())->data; + uint32_t result = guestrelease_cabi_realloc(get_app(), ptr ? (uint8_t*)ptr-linmem : 0, old_size, align, new_size); + return result+linmem; +} + +// Import IF strings +// Func a GuestImport +extern void fooX3AfooX2FstringsX00a(uint8_t *arg0, size_t arg1); +void fooX3AfooX2Fstrings__a(void*app,U32 arg0,U32 arg1) { + uint8_t *linmem = guestrelease_memory(get_app())->data; + fooX3AfooX2FstringsX00a(linmem+arg0, arg1); +} + +// Func b GuestImport +extern void fooX3AfooX2FstringsX00b(uint8_t *arg0); +void fooX3AfooX2Fstrings__b(void*app,U32 arg0) { + uint8_t *linmem = guestrelease_memory(get_app())->data; + static size_t result[2]; + fooX3AfooX2FstringsX00b((uint8_t*)&result); + uint32_t *result_out = (uint32_t*)(linmem+arg0); + result_out[0] = ((uint8_t*)(result[0]))-linmem; + result_out[1] = result[1]; +} +// Func c GuestImport +extern void fooX3AfooX2FstringsX00c(uint8_t *arg0, size_t arg1, + uint8_t *arg2, size_t arg3, + uint8_t *arg4); +void fooX3AfooX2Fstrings__c(void*app,U32 arg0,U32 arg1,U32 arg2,U32 arg3,U32 arg4) { + uint8_t *linmem = guestrelease_memory(get_app())->data; + static size_t result[2]; + fooX3AfooX2FstringsX00c(linmem+arg0, arg1, linmem+arg2, arg3, (uint8_t*)&result); + uint32_t *result_out = (uint32_t*)(linmem+arg4); + result_out[0] = ((uint8_t*)(result[0]))-linmem; + result_out[1] = result[1]; +} +// Export IF strings +// Func a GuestExport +__attribute__ ((visibility ("default"))) +void fooX3AfooX2FstringsX23a(uint8_t *arg0, size_t arg1) { + uint8_t *linmem = guestrelease_memory(get_app())->data; + guestrelease_fooX3AfooX2FstringsX23a(get_app(), arg0-linmem, arg1); +} +// Func b GuestExport +__attribute__ ((visibility ("default"))) uint8_t * +fooX3AfooX2FstringsX23b() { + uint8_t *linmem = guestrelease_memory(get_app())->data; + uint32_t result = guestrelease_fooX3AfooX2FstringsX23b(get_app()); + static size_t ret_area[3]; + ret_area[0] = (size_t)(((uint32_t*)(linmem+result))[0]+linmem); + ret_area[1] = ((uint32_t*)(linmem+result))[1]; + ret_area[2] = result; + return (uint8_t*)ret_area; +} +__attribute__ ((visibility ("default"))) +void cabi_post_fooX3AfooX2FstringsX23b(uint8_t * arg0) { + //uint8_t *linmem = guestrelease_memory(get_app())->data; + guestrelease_cabi_post_fooX583AfooX582FstringsX5823b(get_app(), ((size_t*)arg0)[2]); +} +// Func c GuestExport +__attribute__ ((visibility ("default"))) +uint8_t * fooX3AfooX2FstringsX23c(uint8_t * arg0, size_t arg1, uint8_t *arg2, size_t arg3) { + uint8_t *linmem = guestrelease_memory(get_app())->data; + uint32_t result = guestrelease_fooX3AfooX2FstringsX23c(get_app(), arg0-linmem, arg1, arg2-linmem, arg3); + static size_t ret_area[3]; + ret_area[0] = (size_t)(((uint32_t*)(linmem+result))[0]+linmem); + ret_area[1] = ((uint32_t*)(linmem+result))[1]; + ret_area[2] = result; + return (uint8_t*)ret_area; +} +__attribute__ ((visibility ("default"))) +extern void +cabi_post_fooX3AfooX2FstringsX23c(uint8_t * arg0) { + //uint8_t *linmem = guestrelease_memory(get_app())->data; + guestrelease_cabi_post_fooX583AfooX582FstringsX5823c(get_app(), ((size_t*)arg0)[2]); +} diff --git a/crates/cpp/tests/native_strings/w2c2/wasi_dummy.c b/crates/cpp/tests/native_strings/w2c2/wasi_dummy.c new file mode 100644 index 000000000..3e587b9ef --- /dev/null +++ b/crates/cpp/tests/native_strings/w2c2/wasi_dummy.c @@ -0,0 +1,13 @@ +#include "w2c2_guest.h" + +U32 wasi_snapshot_preview1__args_get(void*,U32,U32) { + abort(); +} + +U32 wasi_snapshot_preview1__args_sizes_get(void*,U32,U32) { + abort(); +} + +void wasi_snapshot_preview1__proc_exit(void*,U32) { + abort(); +} diff --git a/crates/cpp/tests/native_strings/wamr/.gitignore b/crates/cpp/tests/native_strings/wamr/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/crates/cpp/tests/native_strings/wamr/.gitignore @@ -0,0 +1 @@ +/build diff --git a/crates/cpp/tests/native_strings/wamr/CMakeLists.txt b/crates/cpp/tests/native_strings/wamr/CMakeLists.txt new file mode 100644 index 000000000..9437f4ba0 --- /dev/null +++ b/crates/cpp/tests/native_strings/wamr/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.14) +project(wasm_executor) + +set (WAMR_BUILD_PLATFORM "linux") +set (WAMR_BUILD_TARGET "X86_64") +set (WAMR_BUILD_INTERP 1) +set (WAMR_BUILD_FAST_INTERP 0) +set (WAMR_BUILD_JIT 0) +set (WAMR_BUILD_FAST_JIT 0) +set (WAMR_BUILD_AOT 0) +set (WAMR_BUILD_LIBC_BUILTIN 1) +set (WAMR_BUILD_LIBC_WASI 1) +set (WAMR_BUILD_SIMD 1) +# set (WAMR_BUILD_LIB_WASI_THREADS 1) +set (WAMR_ROOT_DIR ../../wasm-micro-runtime) + +include (${WAMR_ROOT_DIR}/build-scripts/runtime_lib.cmake) +add_library(vmlib ${WAMR_RUNTIME_LIB_SOURCE}) + +add_library(strings SHARED + wamr_env.c the-world_bridge.c + ${WAMR_ROOT_DIR}/core/shared/utils/uncommon/bh_read_file.c) +target_include_directories(strings PUBLIC ${CMAKE_CURRENT_LIST_DIR} ${WAMR_ROOT_DIR}/core/shared/utils/uncommon) +target_link_libraries (strings vmlib) diff --git a/crates/cpp/tests/native_strings/wamr/Makefile b/crates/cpp/tests/native_strings/wamr/Makefile new file mode 100644 index 000000000..59bb4273b --- /dev/null +++ b/crates/cpp/tests/native_strings/wamr/Makefile @@ -0,0 +1,23 @@ +WIT_BINDGEN=../../../../../target/debug/wit-bindgen + +all: libstrings.so guest_release.wasm + +libstrings.so: + mkdir build + (cd build; cmake .. ; make) + +guest_release.wasm: ../the_world.cpp ../guest.cpp + /opt/wasi-sdk/bin/clang++ -o $@ $^ -I../../../helper-types -g0 -O3 + +clean: + -rm -r build guest_release.wasm + +run: + LD_LIBRARY_PATH=. ../app-strings + +# valgrind: +# LD_LIBRARY_PATH=. valgrind ../app-strings + +# not yet up to the task +#the-world_bridge.c: $(WIT_BINDGEN) +# $(WIT_BINDGEN) bridge ../wit --instance guestrelease --include w2c2_guest.h diff --git a/crates/cpp/tests/native_strings/wamr/libstrings.so b/crates/cpp/tests/native_strings/wamr/libstrings.so new file mode 120000 index 000000000..087edba73 --- /dev/null +++ b/crates/cpp/tests/native_strings/wamr/libstrings.so @@ -0,0 +1 @@ +build/libstrings.so \ No newline at end of file diff --git a/crates/cpp/tests/native_strings/wamr/the-world_bridge.c b/crates/cpp/tests/native_strings/wamr/the-world_bridge.c new file mode 100644 index 000000000..c6fa3a13c --- /dev/null +++ b/crates/cpp/tests/native_strings/wamr/the-world_bridge.c @@ -0,0 +1,201 @@ + +#include +#include +#include +#include "wamr_env.h" + +#define nullptr 0 + +typedef struct wamr_env wamr_env; + +static wamr_env* instance = nullptr; + +wamr_env* get_app() { + if (!instance) { + instance = create_wamr_env(); + } + return instance; +} + +uint8_t* guestrelease_memory(wamr_env* wamr_env) { + return (uint8_t*)wasm_runtime_addr_app_to_native( + wasm_runtime_get_module_inst(wamr_env->exec_env), 0); +} + +uint32_t guestrelease_cabi_realloc(wamr_env* wamr_env, uint32_t olda, uint32_t olds, uint32_t align, uint32_t new_size) { + WASMFunctionInstanceCommon *cabi_alloc_ptr = wasm_runtime_lookup_function( + wasm_runtime_get_module_inst(wamr_env->exec_env), + "cabi_realloc"); +// "(iiii)i"); + wasm_val_t results[1] = {WASM_INIT_VAL}; + wasm_val_t arguments[4] = {WASM_I32_VAL((int32_t)olda), WASM_I32_VAL((int32_t)olds), WASM_I32_VAL((int32_t)align), WASM_I32_VAL((int32_t)new_size)}; + + if (!wasm_runtime_call_wasm_a(wamr_env->exec_env, cabi_alloc_ptr, 1, results, 4, arguments)) + { + const char *exception; + if ((exception = wasm_runtime_get_exception(wasm_runtime_get_module_inst(wamr_env->exec_env)))) + { + printf("Exception: %s\n", exception); + } + return 0; + } + return results[0].of.i32; +} + +__attribute__ ((visibility ("default"))) +void *cabi_realloc(void *ptr, size_t old_size, size_t align, + size_t new_size) { + uint8_t *linmem = guestrelease_memory(get_app()); + uint32_t result = guestrelease_cabi_realloc(get_app(), ptr ? (uint8_t*)ptr-linmem : 0, old_size, align, new_size); + return result+linmem; +} + +// Import IF strings +// Func a GuestImport +extern void fooX3AfooX2FstringsX00a(uint8_t *arg0, size_t arg1); +void fooX3AfooX2Fstrings__a(void*app,uint8_t* arg0,uint32_t arg1) { + //uint8_t *linmem = guestrelease_memory(get_app()); + fooX3AfooX2FstringsX00a(arg0, arg1); +} + +// Func b GuestImport +extern void fooX3AfooX2FstringsX00b(uint8_t *arg0); +void fooX3AfooX2Fstrings__b(void*app,uint8_t* arg0) { + uint8_t *linmem = guestrelease_memory(get_app()); + static size_t result[2]; + fooX3AfooX2FstringsX00b((uint8_t*)&result); + uint32_t *result_out = (uint32_t*)(arg0); + result_out[0] = ((uint8_t*)(result[0]))-linmem; + result_out[1] = result[1]; +} +// Func c GuestImport +extern void fooX3AfooX2FstringsX00c(uint8_t *arg0, size_t arg1, + uint8_t *arg2, size_t arg3, + uint8_t *arg4); +void fooX3AfooX2Fstrings__c(void*app,uint8_t* arg0,uint32_t arg1,uint8_t* arg2,uint32_t arg3,uint8_t* arg4) { + uint8_t *linmem = guestrelease_memory(get_app()); + static size_t result[2]; + fooX3AfooX2FstringsX00c(arg0, arg1, arg2, arg3, (uint8_t*)&result); + uint32_t *result_out = (uint32_t*)(arg4); + result_out[0] = ((uint8_t*)(result[0]))-linmem; + result_out[1] = result[1]; +} +// Export IF strings +// Func a GuestExport +__attribute__ ((visibility ("default"))) +void fooX3AfooX2FstringsX23a(uint8_t *arg0, size_t arg1) { + wamr_env *wamr_env = get_app(); + uint8_t *linmem = guestrelease_memory(wamr_env); + WASMFunctionInstanceCommon *func_ptr = wasm_runtime_lookup_function( + wasm_runtime_get_module_inst(wamr_env->exec_env), + "foo:foo/strings#a"); + wasm_val_t arguments[2] = {WASM_I32_VAL((int32_t)(arg0-linmem)), WASM_I32_VAL((int32_t)arg1)}; + if (!wasm_runtime_call_wasm_a(wamr_env->exec_env, func_ptr, 0, nullptr, 2, arguments)) + { + const char *exception; + if ((exception = wasm_runtime_get_exception(wasm_runtime_get_module_inst(wamr_env->exec_env)))) + { + printf("Exception: %s\n", exception); + } + } +} +// Func b GuestExport +__attribute__ ((visibility ("default"))) uint8_t * +fooX3AfooX2FstringsX23b() { + wamr_env *wamr_env = get_app(); + uint8_t *linmem = guestrelease_memory(wamr_env); + WASMFunctionInstanceCommon *func_ptr = wasm_runtime_lookup_function( + wasm_runtime_get_module_inst(wamr_env->exec_env), + "foo:foo/strings#b"); + wasm_val_t results[1] = {WASM_INIT_VAL}; + if (!wasm_runtime_call_wasm_a(wamr_env->exec_env, func_ptr, 1, results, 0, nullptr)) + { + const char *exception; + if ((exception = wasm_runtime_get_exception(wasm_runtime_get_module_inst(wamr_env->exec_env)))) + { + printf("Exception: %s\n", exception); + } + } + uint32_t result = results[0].of.i32; + static size_t ret_area[3]; + ret_area[0] = (size_t)(((uint32_t*)(linmem+result))[0]+linmem); + ret_area[1] = ((uint32_t*)(linmem+result))[1]; + ret_area[2] = result; + return (uint8_t*)ret_area; +} +__attribute__ ((visibility ("default"))) +void cabi_post_fooX3AfooX2FstringsX23b(uint8_t * arg0) { + wamr_env *wamr_env = get_app(); + // uint8_t *linmem = guestrelease_memory(wamr_env); + WASMFunctionInstanceCommon *func_ptr = wasm_runtime_lookup_function( + wasm_runtime_get_module_inst(wamr_env->exec_env), + "cabi_post_fooX3AfooX2FstringsX23b"); + wasm_val_t arguments[1] = {WASM_I32_VAL((int32_t)(((size_t*)arg0)[2]))}; + if (!wasm_runtime_call_wasm_a(wamr_env->exec_env, func_ptr, 0, nullptr, 1, arguments)) + { + const char *exception; + if ((exception = wasm_runtime_get_exception(wasm_runtime_get_module_inst(wamr_env->exec_env)))) + { + printf("Exception: %s\n", exception); + } + } +} +// Func c GuestExport +__attribute__ ((visibility ("default"))) +uint8_t * fooX3AfooX2FstringsX23c(uint8_t * arg0, size_t arg1, uint8_t *arg2, size_t arg3) { + wamr_env *wamr_env = get_app(); + uint8_t *linmem = guestrelease_memory(wamr_env); + WASMFunctionInstanceCommon *func_ptr = wasm_runtime_lookup_function( + wasm_runtime_get_module_inst(wamr_env->exec_env), + "foo:foo/strings#c"); + wasm_val_t results[1] = {WASM_INIT_VAL}; + wasm_val_t arguments[4] = {WASM_I32_VAL((int32_t)(arg0-linmem)), WASM_I32_VAL((int32_t)arg1), WASM_I32_VAL((int32_t)(arg2-linmem)), WASM_I32_VAL((int32_t)arg3)}; + + if (!wasm_runtime_call_wasm_a(wamr_env->exec_env, func_ptr, 1, results, 4, arguments)) + { + const char *exception; + if ((exception = wasm_runtime_get_exception(wasm_runtime_get_module_inst(wamr_env->exec_env)))) + { + printf("Exception: %s\n", exception); + } + return 0; + } + uint32_t result = results[0].of.i32; + // arg0-linmem, arg1, arg2-linmem, arg3); + static size_t ret_area[3]; + ret_area[0] = (size_t)(((uint32_t*)(linmem+result))[0]+linmem); + ret_area[1] = ((uint32_t*)(linmem+result))[1]; + ret_area[2] = result; + return (uint8_t*)ret_area; +} +__attribute__ ((visibility ("default"))) +extern void +cabi_post_fooX3AfooX2FstringsX23c(uint8_t * arg0) { + wamr_env *wamr_env = get_app(); + // uint8_t *linmem = guestrelease_memory(wamr_env); + WASMFunctionInstanceCommon *func_ptr = wasm_runtime_lookup_function( + wasm_runtime_get_module_inst(wamr_env->exec_env), + "cabi_post_fooX3AfooX2FstringsX23c"); + wasm_val_t arguments[1] = {WASM_I32_VAL((int32_t)(((size_t*)arg0)[2]))}; + if (!wasm_runtime_call_wasm_a(wamr_env->exec_env, func_ptr, 0, nullptr, 1, arguments)) + { + const char *exception; + if ((exception = wasm_runtime_get_exception(wasm_runtime_get_module_inst(wamr_env->exec_env)))) + { + printf("Exception: %s\n", exception); + } + } +} + +//#include "executor.h" + +void register_functions() { + static NativeSymbol foo_foo_strings_funs[] = { + {"a", (void *)fooX3AfooX2Fstrings__a, "(*~)", nullptr}, + {"b", (void *)fooX3AfooX2Fstrings__b, "(*)", nullptr}, + {"c", (void *)fooX3AfooX2Fstrings__c, "(*~*~*)", nullptr}, + }; + wasm_runtime_register_natives("foo:foo/strings", foo_foo_strings_funs, + sizeof(foo_foo_strings_funs) / + sizeof(NativeSymbol)); +} diff --git a/crates/cpp/tests/native_strings/wamr/wamr_env.c b/crates/cpp/tests/native_strings/wamr/wamr_env.c new file mode 100644 index 000000000..9ba2efea4 --- /dev/null +++ b/crates/cpp/tests/native_strings/wamr/wamr_env.c @@ -0,0 +1,86 @@ +/* + * Adapted from wasm-micro-runtime/samples/basic/src/main.c + * + * Copyright (C) 2019 Intel Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include "wamr_env.h" +#include "bh_read_file.h" + +#define nullptr 0 + +struct wamr_env *create_wamr_env() { + struct wamr_env *result = (struct wamr_env *)malloc(sizeof(struct wamr_env)); + char const *wasm_path = "guest_release.wasm"; + const uint32_t stack_size = 65536, heap_size = 2 * stack_size; + uint32_t buf_size; + + if (!result) + return nullptr; + memset(result, 0, sizeof *result); + + RuntimeInitArgs init_args; + memset(&init_args, 0, sizeof(RuntimeInitArgs)); + + init_args.mem_alloc_type = Alloc_With_Pool; + init_args.mem_alloc_option.pool.heap_buf = result->global_heap_buf; + init_args.mem_alloc_option.pool.heap_size = sizeof(result->global_heap_buf); + init_args.running_mode = Mode_Interp; + if (!wasm_runtime_full_init(&init_args)) { + printf("Init runtime environment failed.\n"); + return result; + } + + register_functions(); + + wasm_runtime_set_log_level(WASM_LOG_LEVEL_VERBOSE); + + result->buffer = bh_read_file_to_buffer(wasm_path, &buf_size); + + if (!result->buffer) { + printf("Open wasm app file [%s] failed.\n", wasm_path); + return result; + } + + result->module = + wasm_runtime_load((uint8 *)result->buffer, buf_size, result->error_buf, + sizeof(result->error_buf)); + if (!result->module) { + printf("Load wasm module failed. error: %s\n", result->error_buf); + return result; + } + + result->module_inst = + wasm_runtime_instantiate(result->module, stack_size, heap_size, + result->error_buf, sizeof(result->error_buf)); + + if (!result->module_inst) { + printf("Instantiate wasm module failed. error: %s\n", result->error_buf); + return result; + } + + result->exec_env = + wasm_runtime_create_exec_env(result->module_inst, stack_size); + if (!result->exec_env) { + printf("Create wasm execution environment failed.\n"); + } + + return result; +} + +void free_wamr_env(struct wamr_env *result) { + if (!result) + return; + if (result->exec_env) + wasm_runtime_destroy_exec_env(result->exec_env); + if (result->module_inst) { + wasm_runtime_deinstantiate(result->module_inst); + } + if (result->module) + wasm_runtime_unload(result->module); + if (result->buffer) + BH_FREE(result->buffer); + wasm_runtime_destroy(); + free(result); +} diff --git a/crates/cpp/tests/native_strings/wamr/wamr_env.h b/crates/cpp/tests/native_strings/wamr/wamr_env.h new file mode 100644 index 000000000..717d52c68 --- /dev/null +++ b/crates/cpp/tests/native_strings/wamr/wamr_env.h @@ -0,0 +1,18 @@ +#pragma once +#include "wasm_c_api.h" +#include "wasm_export.h" + +void register_functions(); + +struct wamr_env { + char global_heap_buf[512 * 1024]; + char *buffer; + char error_buf[128]; + + wasm_module_t module; + wasm_module_inst_t module_inst; + wasm_exec_env_t exec_env; +}; + +struct wamr_env *create_wamr_env(); +void free_wamr_env(struct wamr_env *); diff --git a/crates/cpp/tests/native_strings/wit/strings.wit b/crates/cpp/tests/native_strings/wit/strings.wit new file mode 120000 index 000000000..ce038e736 --- /dev/null +++ b/crates/cpp/tests/native_strings/wit/strings.wit @@ -0,0 +1 @@ +../../../../../tests/codegen/strings.wit \ No newline at end of file diff --git a/crates/cpp/tests/smoke_details/result8.wit b/crates/cpp/tests/smoke_details/result8.wit new file mode 100644 index 000000000..3ece74f19 --- /dev/null +++ b/crates/cpp/tests/smoke_details/result8.wit @@ -0,0 +1,9 @@ +package test:test; + +interface a { + f: func() -> result<_, u8>; +} + +world result8 { + export a; +} diff --git a/crates/cpp/tests/symmetric.rs b/crates/cpp/tests/symmetric.rs new file mode 100644 index 000000000..e4f041157 --- /dev/null +++ b/crates/cpp/tests/symmetric.rs @@ -0,0 +1,357 @@ +use std::{ + fs::{self, File}, + io::{self, Write}, + path::PathBuf, + process::Command, +}; + +use wit_bindgen_core::wit_parser::{Resolve, WorldId}; + +fn tester_source_file(dir_name: &str, tester_source_dir: &PathBuf) -> Option { + let mut tester_source_file = tester_source_dir.clone(); + tester_source_file.push(&format!("{dir_name}.rs")); + if matches!(std::fs::exists(&tester_source_file), Ok(true)) { + Some(tester_source_file) + } else { + None + } +} + +fn create_cargo_files( + dir_name: &str, + out_dir: &PathBuf, + toplevel: &PathBuf, + source_files: &PathBuf, + tester_source_dir: &PathBuf, +) -> io::Result<()> { + let Some(tester_source_file) = tester_source_file(dir_name, tester_source_dir) else { + println!("Skipping {}", dir_name); + return Ok(()); + }; + + let mut out_dir = out_dir.clone(); + out_dir.push(dir_name); + // println!("{cpp:?} {out_dir:?}"); + + let mut dir = source_files.clone(); + dir.push(dir_name); + + drop(std::fs::remove_dir_all(&out_dir)); + std::fs::create_dir_all(&out_dir)?; + let mut testee_dir = out_dir.clone(); + testee_dir.push("rust"); + std::fs::create_dir(&testee_dir)?; + let testee_cargo = format!( + "[package]\n\ + name = \"{dir_name}\"\n\ + publish = false\n\ + edition = \"2021\"\n\ + \n\ + [dependencies]\n\ + wit-bindgen = {{ path = \"{toplevel}/crates/guest-rust\" }}\n\ + test-rust-wasm = {{ path = \"{toplevel}/crates/cpp/tests/symmetric_tests/test-rust-wasm\" }}\n\ + futures = \"0.3\"\n\ + once_cell = \"1.20\"\n\ + \n\ + [lib]\n\ + crate-type = [\"cdylib\"]\n\ + ", + toplevel = toplevel.display() + ); + let mut filename = testee_dir.clone(); + filename.push("Cargo.toml"); + File::create(&filename)?.write_all(testee_cargo.as_bytes())?; + drop(testee_cargo); + // let mut testee_dir = out_dir.clone(); + // testee_dir.push("rust"); + //let mut filename = testee_dir.clone(); + filename.pop(); + filename.push("src"); + std::fs::create_dir(&filename)?; + filename.push(format!("lib.rs")); + let mut original = dir.clone(); + original.push("wasm.rs"); + std::os::unix::fs::symlink(original, filename)?; + + let tester_cargo = format!( + "[package]\n\ + name = \"tester-{dir_name}\"\n\ + publish = false\n\ + edition = \"2021\"\n\ + \n\ + [dependencies]\n\ + wit-bindgen = {{ path = \"{toplevel}/crates/guest-rust\" }}\n\ + {dir_name} = {{ path = \"rust\" }}\n\ + futures = \"0.3\"\n\ + once_cell = \"1.20\"\n\ + ", + toplevel = toplevel.display() + ); + let mut filename = out_dir.clone(); + filename.push("Cargo.toml"); + File::create(&filename)?.write_all(tester_cargo.as_bytes())?; + filename.pop(); + // let mut filename = out_dir.clone(); + filename.push("src"); + std::fs::create_dir(&filename)?; + filename.push(format!("main.rs")); + std::os::unix::fs::symlink(tester_source_file, &filename)?; + + Ok(()) +} + +fn tests( + dir_name: &str, + out_dir: &PathBuf, + _toplevel: &PathBuf, + source_files: &PathBuf, + tester_source_dir: &PathBuf, +) -> io::Result<()> { + // modelled after wit-bindgen/tests/runtime/main.rs + let Some(_tester_source_file) = tester_source_file(dir_name, tester_source_dir) else { + println!("Skipping {}", dir_name); + return Ok(()); + }; + + let mut dir = source_files.clone(); + dir.push(dir_name); + + // let mut rust = Vec::new(); + let mut cpp = Vec::new(); + for file in dir.read_dir()? { + let path = file?.path(); + match path.extension().and_then(|s| s.to_str()) { + // Some("rs") => rust.push(path), + Some("cpp") => cpp.push(path), + _ => {} + } + } + + let mut out_dir = out_dir.clone(); + out_dir.push(dir_name); + // println!("{cpp:?} {out_dir:?}"); + + let mut testee_dir = out_dir.clone(); + testee_dir.push("rust"); + let mut filename = testee_dir.clone(); + filename.push("src"); + // std::fs::create_dir(&filename)?; + filename.push(format!("lib.rs")); + let mut original = dir.clone(); + original.push("wasm.rs"); + // std::os::unix::fs::symlink(original, filename)?; + + let mut filename = out_dir.clone(); + filename.push("src"); + // std::fs::create_dir(&filename)?; + filename.push(format!("main.rs")); + // std::os::unix::fs::symlink(tester_source_file, &filename)?; + + let mut cmd = Command::new("cargo"); + cmd.arg("build") + .current_dir(testee_dir) + .env("RUSTFLAGS", "-Ltarget/debug") + .env("SYMMETRIC_ABI", "1") + .env("WIT_BINDGEN_DEBUG", "1"); + let status = cmd.status().unwrap(); + assert!(status.success()); + + let mut cmd = Command::new("cargo"); + cmd.arg("run") + .current_dir(&out_dir) + .env("RUSTFLAGS", "-Ltarget/debug") + .env("SYMMETRIC_ABI", "1") + .env("WIT_BINDGEN_DEBUG", "1"); + let status = cmd.status().unwrap(); + assert!(status.success()); + + for path in cpp.iter() { + let (mut resolve, mut world) = resolve_wit_dir(&dir); + let world_name = &resolve.worlds[world].name; + let cpp_dir = out_dir.join("cpp"); + drop(fs::remove_dir_all(&cpp_dir)); + fs::create_dir_all(&cpp_dir).unwrap(); + + let snake = world_name.replace("-", "_"); + let mut files = Default::default(); + let mut opts = wit_bindgen_cpp::Opts::default(); + opts.symmetric = true; + if let Some(path) = path.file_name().and_then(|s| s.to_str()) { + if path.contains(".new.") { + opts.new_api = true; + } + } + let mut cpp = opts.build(); + cpp.apply_resolve_options(&mut resolve, &mut world); + cpp.generate(&resolve, world, &mut files).unwrap(); + + for (file, contents) in files.iter() { + let dst = cpp_dir.join(file); + fs::write(dst, contents).unwrap(); + } + + let compiler = "clang++"; + let mut cmd = Command::new(compiler); + let out_name = cpp_dir.join(format!("lib{}.so", dir_name)); + cmd.arg(path) + .arg(cpp_dir.join(format!("{snake}.cpp"))) + .arg("-shared") + .arg("-fPIC") + .arg("-I") + .arg(&cpp_dir) + .arg("-I") + .arg(&(String::from(env!("CARGO_MANIFEST_DIR")) + "/test_headers")) + .arg("-Wall") + .arg("-Wextra") + .arg("-Wno-unused-parameter") + .arg("-std=c++17") + .arg("-g") + .arg("-o") + .arg(&out_name); + println!("{:?}", cmd); + let output = match cmd.output() { + Ok(output) => output, + Err(e) => panic!("failed to spawn compiler: {}", e), + }; + + if !output.status.success() { + println!("status: {}", output.status); + println!("stdout: ------------------------------------------"); + println!("{}", String::from_utf8_lossy(&output.stdout)); + println!("stderr: ------------------------------------------"); + println!("{}", String::from_utf8_lossy(&output.stderr)); + panic!("failed to compile"); + } else { + let mut tester = out_dir.clone(); + tester.pop(); + tester.push("target"); + tester.push("debug"); + tester.push(&format!("tester-{dir_name}")); + let run = Command::new(tester) + .env("LD_LIBRARY_PATH", cpp_dir) + .output(); + match run { + Ok(output) => { + if !output.status.success() { + println!("status: {}", output.status); + println!("stdout: ------------------------------------------"); + println!("{}", String::from_utf8_lossy(&output.stdout)); + println!("stderr: ------------------------------------------"); + println!("{}", String::from_utf8_lossy(&output.stderr)); + panic!("failed to run"); + } + } + Err(e) => panic!("failed to run tester: {}", e), + } + } + } + + Ok(()) +} + +fn resolve_wit_dir(dir: &PathBuf) -> (Resolve, WorldId) { + let mut resolve = Resolve::new(); + let (pkg, _files) = resolve.push_path(dir).unwrap(); + let world = resolve.select_world(pkg, None).unwrap(); + (resolve, world) +} + +#[test] +fn symmetric_integration() -> io::Result<()> { + let mut out_dir = std::env::current_exe()?; + out_dir.pop(); + out_dir.pop(); + out_dir.pop(); + out_dir.push("symmetric-tests"); + if !out_dir.try_exists().unwrap_or(false) { + std::fs::create_dir_all(&out_dir)?; + } + + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + + let mut toplevel = manifest_dir.clone(); + toplevel.pop(); + toplevel.pop(); + + let mut test_link = out_dir.clone(); + test_link.push("tests"); + if !test_link.try_exists().unwrap_or(false) { + let mut original = toplevel.clone(); + original.push("tests"); + std::os::unix::fs::symlink(original, &test_link)?; + } + + let mut source_files = toplevel.clone(); + source_files.push("tests"); + source_files.push("runtime"); + + let mut tester_source_dir = manifest_dir.clone(); + tester_source_dir.push("tests"); + tester_source_dir.push("symmetric_tests"); + + let default_testcases = vec![ + "flavorful", + "lists", + "many_arguments", + "numbers", + "options", + "records", + "results", + "smoke", + "strings", + ]; + let testcases: Vec = std::env::var_os("SYMMETRIC_TESTS").map_or_else( + || { + default_testcases + .iter() + .map(|s| s.to_string()) + .collect::>() + }, + |var| { + var.into_string() + .expect("UTF8 expected") + .split(',') + .map(|s| s.to_string()) + .collect() + }, + ); + // create workspace + { + let mut workspace = format!( + "[workspace]\n\ + resolver = \"2\"\n\ + \n\ + members = [\n" + ); + for dir_name in testcases.iter() { + if tester_source_file(dir_name, &tester_source_dir).is_some() { + workspace.push_str(&format!( + " \"{}\",\n \"{}/rust\",\n", + dir_name, dir_name + )); + } + create_cargo_files( + dir_name, + &out_dir, + &toplevel, + &source_files, + &tester_source_dir, + )?; + } + workspace.push_str("]\n"); + let mut filename = out_dir.clone(); + filename.push("Cargo.toml"); + File::create(&filename)?.write_all(workspace.as_bytes())?; + } + for dir_name in testcases { + tests( + &dir_name, + &out_dir, + &toplevel, + &source_files, + &tester_source_dir, + )?; + } + + Ok(()) +} diff --git a/crates/cpp/tests/symmetric_async/Cargo.lock b/crates/cpp/tests/symmetric_async/Cargo.lock new file mode 100644 index 000000000..e309431df --- /dev/null +++ b/crates/cpp/tests/symmetric_async/Cargo.lock @@ -0,0 +1,224 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "async_module" +version = "0.1.0" +dependencies = [ + "dummy-rt", + "futures", + "sleep", + "symmetric_executor", + "symmetric_stream", + "wit-bindgen-symmetric-rt", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "dummy-rt" +version = "0.1.0" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "libc" +version = "0.2.167" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" + +[[package]] +name = "main" +version = "0.1.0" +dependencies = [ + "async_module", + "symmetric_executor", + "wit-bindgen-symmetric-rt", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "sleep" +version = "0.1.0" +dependencies = [ + "symmetric_executor", +] + +[[package]] +name = "symmetric_executor" +version = "0.1.0" +dependencies = [ + "dummy-rt", + "futures", + "libc", +] + +[[package]] +name = "symmetric_stream" +version = "0.1.0" +dependencies = [ + "dummy-rt", + "symmetric_executor", + "wit-bindgen-symmetric-rt", +] + +[[package]] +name = "syn" +version = "2.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "wit-bindgen-symmetric-rt" +version = "0.36.0" +dependencies = [ + "dummy-rt", + "futures", +] diff --git a/crates/cpp/tests/symmetric_async/Cargo.toml b/crates/cpp/tests/symmetric_async/Cargo.toml new file mode 100644 index 000000000..50596891a --- /dev/null +++ b/crates/cpp/tests/symmetric_async/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +members = [ "async_module", "main","sleep"] +resolver = "2" diff --git a/crates/cpp/tests/symmetric_async/README.md b/crates/cpp/tests/symmetric_async/README.md new file mode 100644 index 000000000..d11afed03 --- /dev/null +++ b/crates/cpp/tests/symmetric_async/README.md @@ -0,0 +1,26 @@ +# Native async design + +## With the canonical ABI + +Imported asynchronous functions take two arguments: A pointer to the argument buffer and a pointer to +the result buffer. They return an i32, the two highest bits indicate the state of the async call, +the lower bits contain the task id to wait for completion. The argument buffer is freed by the callee. + +Exported asynchronous function receive their arguments normally in registers, the return value is set via +"[task-return]name" and it returns either null or a pointer to the state to pass to the callback +once the blocking task completes. + +## Proposal for native + +Symmetric asynchronous functions receive their arguments normally, if they return a value they pass +a return value buffer and they return a pointer to the object (EventSubscription) to wait on +or null (completed). + +If the bottommost bit of the return value is set (objects have an even address) the call wasn't started (backpressure) and should be retried once the returned event gets active. + +This combines the most efficient parts of the import and export (minimizing allocations and calls). + +## Executor + +See the crates/symmetric_executor directory. The main functions are create_timer, create_event, subscribe, +register_callback and run. diff --git a/crates/cpp/tests/symmetric_async/async_cpp/.gitignore b/crates/cpp/tests/symmetric_async/async_cpp/.gitignore new file mode 100644 index 000000000..925b4b450 --- /dev/null +++ b/crates/cpp/tests/symmetric_async/async_cpp/.gitignore @@ -0,0 +1,2 @@ +/*.o +/*.so diff --git a/crates/cpp/tests/symmetric_async/async_cpp/Makefile b/crates/cpp/tests/symmetric_async/async_cpp/Makefile new file mode 100644 index 000000000..b6ce50b3a --- /dev/null +++ b/crates/cpp/tests/symmetric_async/async_cpp/Makefile @@ -0,0 +1,16 @@ +CXXFLAGS=-g -Isrc -fPIC -I../../../../symmetric_executor/cpp-client -I../../../helper-types +LDFLAGS=-L../../../../symmetric_executor/cpp-client \ + -L../../../../symmetric_executor/target/debug \ + -L../target/debug/deps + +libasync_module.so: async_module.o middle.o + $(CXX) -shared -o $@ $^ $(LDFLAGS) -lruntime -lsleep -lsymmetric_executor -lsymmetric_stream + +%.o: src/%.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $^ + +clean: + -rm libasync_module.so async_module.o middle.o + +run: libasync_module.so + LD_LIBRARY_PATH=.:../target/debug/deps ../target/debug/main diff --git a/crates/cpp/tests/symmetric_async/async_cpp/generate.sh b/crates/cpp/tests/symmetric_async/async_cpp/generate.sh new file mode 100644 index 000000000..f2926eec6 --- /dev/null +++ b/crates/cpp/tests/symmetric_async/async_cpp/generate.sh @@ -0,0 +1,2 @@ +#!/bin/sh +(cd src;../../../../../../target/debug/wit-bindgen cpp ../../wit/async_module.wit --symmetric --new-api) diff --git a/crates/cpp/tests/symmetric_async/async_cpp/src/async_module.cpp b/crates/cpp/tests/symmetric_async/async_cpp/src/async_module.cpp new file mode 100644 index 000000000..c05b946ad --- /dev/null +++ b/crates/cpp/tests/symmetric_async/async_cpp/src/async_module.cpp @@ -0,0 +1,51 @@ +// Generated by `wit-bindgen` 0.3.0. DO NOT EDIT! + +// Ensure that the *_component_type.o object is linked in +#ifdef __wasm32__ +extern void __component_type_object_force_link_async_module(void); +void __component_type_object_force_link_async_module_public_use_in_this_compilation_unit(void) { + __component_type_object_force_link_async_module(); +} +#endif +#include "async_module_cpp.h" +#include "module_cpp.h" +#include "async_support.h" +#include // realloc +#include + +extern "C" void *cabi_realloc(void *ptr, size_t old_size, size_t align, size_t new_size); + +__attribute__((__weak__, __export_name__("cabi_realloc"))) +void *cabi_realloc(void *ptr, size_t old_size, size_t align, size_t new_size) { + (void) old_size; + if (new_size == 0) return (void*) align; + void *ret = realloc(ptr, new_size); + if (!ret) abort(); + return ret; +} + +extern "C" void* testX3AtestX2FwaitX00X5BasyncX5Dsleep(int64_t); +std::future test::test::wait::Sleep(uint64_t nanoseconds) +{ + return lift_event(testX3AtestX2FwaitX00X5BasyncX5Dsleep(int64_t(nanoseconds))); +} + +extern "C" +void* testX3AtestX2Fstring_delayX00X5BasyncX5Dforward(uint8_t* arg0, size_t arg1, uint8_t* arg2) +{ + auto len0 = arg1; + + auto store = [arg2](wit::string && result1) { + auto ptr2 = (uint8_t*)(result1.data()); + auto len2 = (size_t)(result1.size()); + result1.leak(); + + *((size_t*)(arg2 + sizeof(void*))) = len2; + *((uint8_t**)(arg2 + 0)) = ptr2; + }; + + auto result1 = exports::test::test::string_delay::Forward(std::string_view((char const*)(arg0), len0)); + return lower_async(std::move(result1), std::move(store)); +} + +// Component Adapters diff --git a/crates/cpp/tests/symmetric_async/async_cpp/src/async_module_cpp.h b/crates/cpp/tests/symmetric_async/async_cpp/src/async_module_cpp.h new file mode 100644 index 000000000..16d0e4c41 --- /dev/null +++ b/crates/cpp/tests/symmetric_async/async_cpp/src/async_module_cpp.h @@ -0,0 +1,16 @@ +// Generated by `wit-bindgen` 0.3.0. DO NOT EDIT! +#ifndef __CPP_GUEST_BINDINGS_ASYNC_MODULE_H +#define __CPP_GUEST_BINDINGS_ASYNC_MODULE_H +#define WIT_SYMMETRIC +#include +#include +#include +#include +#include +namespace test {namespace test {namespace wait {std::future Sleep(uint64_t nanoseconds); +// export_interface Interface(Id { idx: 0 }) +}}} +namespace exports {namespace test {namespace test {namespace string_delay {std::future Forward(std::string_view s); +}}}} + +#endif diff --git a/crates/cpp/tests/symmetric_async/async_cpp/src/middle.cpp b/crates/cpp/tests/symmetric_async/async_cpp/src/middle.cpp new file mode 100644 index 000000000..3ff4782ca --- /dev/null +++ b/crates/cpp/tests/symmetric_async/async_cpp/src/middle.cpp @@ -0,0 +1,22 @@ +#include "async_module_cpp.h" +#include + +std::future exports::test::test::string_delay::Forward(std::string_view s) { + if (s[0]=='A') { + std::promise result; + result.set_value(wit::string::from_view("directly returned")); + return result.get_future(); + } else if (s[0]=='B') { + return std::async(std::launch::async, [](){ + auto delay = ::test::test::wait::Sleep(5ull*1000*1000*1000); + delay.wait(); + return wit::string::from_view("after five seconds"); + }); + } else { + return std::async(std::launch::async, [](){ + auto delay = ::test::test::wait::Sleep(1*1000*1000*1000); + delay.wait(); + return wit::string::from_view("after one second"); + }); + } +} diff --git a/crates/cpp/tests/symmetric_async/async_module/Cargo.toml b/crates/cpp/tests/symmetric_async/async_module/Cargo.toml new file mode 100644 index 000000000..96d7509f2 --- /dev/null +++ b/crates/cpp/tests/symmetric_async/async_module/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "async_module" +version = "0.1.0" +edition = "2021" + +[dependencies] +futures = "0.3.31" +sleep = { path = "../sleep" } +symmetric_executor = { path = "../../../../symmetric_executor" } +symmetric_stream = { path = "../../../../symmetric_executor/symmetric_stream" } +#wit-bindgen = { version = "0.36.0", path = "../../../../guest-rust" } +wit-bindgen-symmetric-rt = { path = "../../../../symmetric_executor/rust-client" } + +[dependencies.wit-bindgen] +package = "dummy-rt" +path = "../../../../symmetric_executor/dummy-rt" + +[lib] +crate-type = ["cdylib"] diff --git a/crates/cpp/tests/symmetric_async/async_module/build.rs b/crates/cpp/tests/symmetric_async/async_module/build.rs new file mode 100644 index 000000000..3096c1795 --- /dev/null +++ b/crates/cpp/tests/symmetric_async/async_module/build.rs @@ -0,0 +1,9 @@ +use std::env; + +fn main() { + let out = env::var_os("OUT_DIR").unwrap(); + println!( + r"cargo:rustc-link-search={}/../../../deps", + out.into_string().unwrap() + ); +} diff --git a/crates/cpp/tests/symmetric_async/async_module/src/async_module.rs b/crates/cpp/tests/symmetric_async/async_module/src/async_module.rs new file mode 100644 index 000000000..d856d5e44 --- /dev/null +++ b/crates/cpp/tests/symmetric_async/async_module/src/async_module.rs @@ -0,0 +1,179 @@ +// Generated by `wit-bindgen` 0.40.0. DO NOT EDIT! +// Options used: +#[allow(dead_code, clippy::all)] +pub mod test { + pub mod test { + + #[allow(dead_code, unused_imports, clippy::all)] + pub mod wait { + #[used] + #[doc(hidden)] + static __FORCE_SECTION_REF: fn() = + super::super::super::__link_custom_section_describing_imports; + + use super::super::super::_rt; + #[allow(unused_unsafe, clippy::all)] + pub async fn async_sleep(nanoseconds: u64) -> () { + unsafe { + #[link(wasm_import_module = "test:test/wait")] + #[link(name = "sleep")] + extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "[async]sleep")] + fn testX3AtestX2FwaitX00X5BasyncX5Dsleep(_: u64) -> *mut u8; + } + ::wit_bindgen_symmetric_rt::async_support::await_result(move || unsafe { + testX3AtestX2FwaitX00X5BasyncX5Dsleep(nanoseconds) + }) + .await; + } + } + } + } +} +#[allow(dead_code, clippy::all)] +pub mod exports { + pub mod test { + pub mod test { + + #[allow(dead_code, unused_imports, clippy::all)] + pub mod string_delay { + #[used] + #[doc(hidden)] + static __FORCE_SECTION_REF: fn() = + super::super::super::super::__link_custom_section_describing_imports; + + use super::super::super::super::_rt; + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_async_forward_cabi( + arg0: *mut u8, + arg1: usize, + arg2: *mut u8, + ) -> *mut u8 { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let result = async move { + let len0 = arg1; + let string0 = String::from( + std::str::from_utf8(std::slice::from_raw_parts(arg0, len0)).unwrap(), + ); + let result = T::async_forward(string0).await; + result + }; + let result = wit_bindgen_symmetric_rt::async_support::first_poll(result, move |result1| { + let vec2 = (result1.into_bytes()).into_boxed_slice(); + let ptr2 = vec2.as_ptr().cast::(); + let len2 = vec2.len(); + ::core::mem::forget(vec2); + let output = arg2.cast::(); + *unsafe { &mut *output } = ptr2 as usize; + *unsafe { &mut *output.add(1) } = len2; + }); + result.cast() + } + pub trait Guest { + async fn async_forward(s: _rt::String) -> _rt::String; + } + #[doc(hidden)] + + macro_rules! __export_test_test_string_delay_cabi{ + ($ty:ident with_types_in $($path_to_types:tt)*) => (const _: () = { + + #[cfg_attr(target_arch = "wasm32", export_name = "[async]forward")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn testX3AtestX2Fstring_delayX00X5BasyncX5Dforward(arg0: *mut u8,arg1: usize,arg2: *mut u8,) -> *mut u8 { + $($path_to_types)*::_export_async_forward_cabi::<$ty>(arg0, arg1, arg2) + } + };); + } + #[doc(hidden)] + pub(crate) use __export_test_test_string_delay_cabi; + } + } + } +} +mod _rt { + #![allow(dead_code, clippy::all)] + + pub fn as_i64(t: T) -> i64 { + t.as_i64() + } + + pub trait AsI64 { + fn as_i64(self) -> i64; + } + + impl<'a, T: Copy + AsI64> AsI64 for &'a T { + fn as_i64(self) -> i64 { + (*self).as_i64() + } + } + + impl AsI64 for i64 { + #[inline] + fn as_i64(self) -> i64 { + self as i64 + } + } + + impl AsI64 for u64 { + #[inline] + fn as_i64(self) -> i64 { + self as i64 + } + } + + #[cfg(target_arch = "wasm32")] + pub fn run_ctors_once() { + wit_bindgen::rt::run_ctors_once(); + } + pub use alloc_crate::string::String; + pub use alloc_crate::vec::Vec; + extern crate alloc as alloc_crate; +} + +/// Generates `#[unsafe(no_mangle)]` functions to export the specified type as +/// the root implementation of all generated traits. +/// +/// For more information see the documentation of `wit_bindgen::generate!`. +/// +/// ```rust +/// # macro_rules! export{ ($($t:tt)*) => (); } +/// # trait Guest {} +/// struct MyType; +/// +/// impl Guest for MyType { +/// // ... +/// } +/// +/// export!(MyType); +/// ``` +#[allow(unused_macros)] +#[doc(hidden)] + +macro_rules! __export_async_module_impl { + ($ty:ident) => (self::export!($ty with_types_in self);); + ($ty:ident with_types_in $($path_to_types_root:tt)*) => ( + $($path_to_types_root)*::exports::test::test::string_delay::__export_test_test_string_delay_cabi!($ty with_types_in $($path_to_types_root)*::exports::test::test::string_delay); + ) +} +#[doc(inline)] +pub(crate) use __export_async_module_impl as export; + +#[cfg(target_arch = "wasm32")] +#[unsafe(link_section = "component-type:wit-bindgen:0.40.0:test:test:async-module:encoded world")] +#[doc(hidden)] +#[allow(clippy::octal_escapes)] +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 278] = *b"\ +\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\x93\x01\x01A\x02\x01\ +A\x04\x01B\x02\x01@\x01\x0bnanosecondsw\x01\0\x04\0\x0c[async]sleep\x01\0\x03\0\x0e\ +test:test/wait\x05\0\x01B\x02\x01@\x01\x01ss\0s\x04\0\x0e[async]forward\x01\0\x04\ +\0\x16test:test/string-delay\x05\x01\x04\0\x16test:test/async-module\x04\0\x0b\x12\ +\x01\0\x0casync-module\x03\0\0\0G\x09producers\x01\x0cprocessed-by\x02\x0dwit-co\ +mponent\x070.227.1\x10wit-bindgen-rust\x060.40.0"; + +#[inline(never)] +#[doc(hidden)] +pub fn __link_custom_section_describing_imports() { + wit_bindgen::rt::maybe_link_cabi_realloc(); +} diff --git a/crates/cpp/tests/symmetric_async/async_module/src/lib.rs b/crates/cpp/tests/symmetric_async/async_module/src/lib.rs new file mode 100644 index 000000000..9973e2af8 --- /dev/null +++ b/crates/cpp/tests/symmetric_async/async_module/src/lib.rs @@ -0,0 +1,21 @@ +mod async_module; + +async_module::export!(Guest with_types_in async_module); + +struct Guest; + +impl async_module::exports::test::test::string_delay::Guest for Guest { + async fn async_forward(s: String) -> String { + match s.as_str() { + "A" => "directly returned".into(), + "B" => { + async_module::test::test::wait::async_sleep(5_000_000_000).await; + "after five seconds".into() + } + _ => { + async_module::test::test::wait::async_sleep(1_000_000_000).await; + "after one second".into() + } + } + } +} diff --git a/crates/cpp/tests/symmetric_async/generate.sh b/crates/cpp/tests/symmetric_async/generate.sh new file mode 100755 index 000000000..ac4e4230c --- /dev/null +++ b/crates/cpp/tests/symmetric_async/generate.sh @@ -0,0 +1,3 @@ +#!/bin/sh +(cd async_module/src ; ../../../../../../target/debug/wit-bindgen rust ../../wit/async_module.wit --async all --symmetric) +cargo fmt diff --git a/crates/cpp/tests/symmetric_async/main/Cargo.toml b/crates/cpp/tests/symmetric_async/main/Cargo.toml new file mode 100644 index 000000000..88a81ae31 --- /dev/null +++ b/crates/cpp/tests/symmetric_async/main/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "main" +version = "0.1.0" +edition = "2021" + +[dependencies] +async_module = { path = "../async_module" } +symmetric_executor = { path = "../../../../symmetric_executor", features = ["trace"] } +wit-bindgen-symmetric-rt = { path = "../../../../symmetric_executor/rust-client" } diff --git a/crates/cpp/tests/symmetric_async/main/build.rs b/crates/cpp/tests/symmetric_async/main/build.rs new file mode 100644 index 000000000..3096c1795 --- /dev/null +++ b/crates/cpp/tests/symmetric_async/main/build.rs @@ -0,0 +1,9 @@ +use std::env; + +fn main() { + let out = env::var_os("OUT_DIR").unwrap(); + println!( + r"cargo:rustc-link-search={}/../../../deps", + out.into_string().unwrap() + ); +} diff --git a/crates/cpp/tests/symmetric_async/main/src/main.rs b/crates/cpp/tests/symmetric_async/main/src/main.rs new file mode 100644 index 000000000..8b1dd8e61 --- /dev/null +++ b/crates/cpp/tests/symmetric_async/main/src/main.rs @@ -0,0 +1,67 @@ +use wit_bindgen_symmetric_rt::{CallbackState, EventSubscription}; + +#[link(name = "async_module")] +extern "C" { + pub fn testX3AtestX2Fstring_delayX00X5BasyncX5Dforward( + addr: *const u8, + len: usize, + results: *mut (), + ) -> *mut (); +} + +extern "C" fn print_result(obj: *mut ()) -> CallbackState { + let addrptr = unsafe { *obj.cast::<*mut u8>() }; + let lenptr = unsafe { obj.byte_add(core::mem::size_of::<*const u8>()) }; + let len = unsafe { *lenptr.cast::() }; + let vec = unsafe { Vec::from_raw_parts(addrptr, len, len) }; + let string = std::str::from_utf8(&vec).unwrap(); + println!("Result {string}"); + CallbackState::Ready +} + +fn main() { + let mut result1: [usize; 2] = [0, 0]; + let handle1 = unsafe { + testX3AtestX2Fstring_delayX00X5BasyncX5Dforward( + "A".as_ptr(), + 1, + result1.as_mut_ptr().cast(), + ) + }; + assert_eq!(handle1, core::ptr::null_mut()); + let vec = unsafe { Vec::from_raw_parts(result1[0] as *mut u8, result1[1], result1[1]) }; + let string = std::str::from_utf8(&vec).unwrap(); + println!("Result {string}"); + + let mut result2: [usize; 2] = [0, 0]; + let handle2 = unsafe { + testX3AtestX2Fstring_delayX00X5BasyncX5Dforward( + "B".as_ptr(), + 1, + result2.as_mut_ptr().cast(), + ) + }; + assert_ne!(handle2, core::ptr::null_mut()); + wit_bindgen_symmetric_rt::register( + unsafe { EventSubscription::from_handle(handle2 as usize) }, + print_result, + result2.as_mut_ptr().cast(), + ); + + let mut result3: [usize; 2] = [0, 0]; + let handle3 = unsafe { + testX3AtestX2Fstring_delayX00X5BasyncX5Dforward( + "C".as_ptr(), + 1, + result3.as_mut_ptr().cast(), + ) + }; + assert_ne!(handle3, core::ptr::null_mut()); + wit_bindgen_symmetric_rt::register( + unsafe { EventSubscription::from_handle(handle3 as usize) }, + print_result, + result3.as_mut_ptr().cast(), + ); + + wit_bindgen_symmetric_rt::run(); +} diff --git a/crates/cpp/tests/symmetric_async/sleep/Cargo.toml b/crates/cpp/tests/symmetric_async/sleep/Cargo.toml new file mode 100644 index 000000000..8d73cef54 --- /dev/null +++ b/crates/cpp/tests/symmetric_async/sleep/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "sleep" +version = "0.1.0" +edition = "2021" + +[dependencies] +symmetric_executor = { path = "../../../../symmetric_executor" } + +[lib] +crate-type = ["cdylib"] diff --git a/crates/cpp/tests/symmetric_async/sleep/build.rs b/crates/cpp/tests/symmetric_async/sleep/build.rs new file mode 100644 index 000000000..3096c1795 --- /dev/null +++ b/crates/cpp/tests/symmetric_async/sleep/build.rs @@ -0,0 +1,9 @@ +use std::env; + +fn main() { + let out = env::var_os("OUT_DIR").unwrap(); + println!( + r"cargo:rustc-link-search={}/../../../deps", + out.into_string().unwrap() + ); +} diff --git a/crates/cpp/tests/symmetric_async/sleep/src/lib.rs b/crates/cpp/tests/symmetric_async/sleep/src/lib.rs new file mode 100644 index 000000000..565ac1eda --- /dev/null +++ b/crates/cpp/tests/symmetric_async/sleep/src/lib.rs @@ -0,0 +1,16 @@ +#[link(name = "symmetric_executor")] +extern "C" { + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E1X2E0X00X5BstaticX5Devent_subscriptionX2Efrom_timeout( + nanoseconds: u64, + ) -> *mut (); +} + +#[no_mangle] +unsafe extern "C" fn testX3AtestX2FwaitX00X5BasyncX5Dsleep( + nanoseconds: u64, + // args: *const (), + // _results: *mut (), +) -> *mut () { + // let nanoseconds = *args.cast::(); + symmetricX3AruntimeX2Fsymmetric_executorX400X2E1X2E0X00X5BstaticX5Devent_subscriptionX2Efrom_timeout(nanoseconds) +} diff --git a/crates/cpp/tests/symmetric_async/wit/async_module.wit b/crates/cpp/tests/symmetric_async/wit/async_module.wit new file mode 100644 index 000000000..76d826324 --- /dev/null +++ b/crates/cpp/tests/symmetric_async/wit/async_module.wit @@ -0,0 +1,14 @@ +package test:test; + +interface string-delay { + forward: async func(s: string) -> string; +} + +interface wait { + sleep: async func(nanoseconds: u64); +} + +world async-module { + import wait; + export string-delay; +} diff --git a/crates/cpp/tests/symmetric_future/Cargo.lock b/crates/cpp/tests/symmetric_future/Cargo.lock new file mode 100644 index 000000000..ea26f3672 --- /dev/null +++ b/crates/cpp/tests/symmetric_future/Cargo.lock @@ -0,0 +1,240 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "dummy-rt" +version = "0.1.0" + +[[package]] +name = "future" +version = "0.1.0" +dependencies = [ + "futures", + "mini-bindgen", + "source", + "symmetric_stream", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "main" +version = "0.1.0" +dependencies = [ + "future", + "symmetric_executor", + "symmetric_stream", + "wit-bindgen-symmetric-rt", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mini-bindgen" +version = "0.1.0" +dependencies = [ + "dummy-rt", + "wit-bindgen-symmetric-rt", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "source" +version = "0.1.0" +dependencies = [ + "symmetric_executor", + "symmetric_stream", + "wit-bindgen-symmetric-rt", +] + +[[package]] +name = "symmetric_executor" +version = "0.1.0" +dependencies = [ + "dummy-rt", + "futures", + "libc", +] + +[[package]] +name = "symmetric_stream" +version = "0.1.0" +dependencies = [ + "dummy-rt", + "symmetric_executor", + "wit-bindgen-symmetric-rt", +] + +[[package]] +name = "syn" +version = "2.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" + +[[package]] +name = "wit-bindgen-symmetric-rt" +version = "0.36.0" +dependencies = [ + "bitflags", + "dummy-rt", + "futures", +] diff --git a/crates/cpp/tests/symmetric_future/Cargo.toml b/crates/cpp/tests/symmetric_future/Cargo.toml new file mode 100644 index 000000000..6b29008fd --- /dev/null +++ b/crates/cpp/tests/symmetric_future/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +members = [ "future","main", "source"] +resolver="2" diff --git a/crates/cpp/tests/symmetric_future/future/Cargo.toml b/crates/cpp/tests/symmetric_future/future/Cargo.toml new file mode 100644 index 000000000..d218ec8a2 --- /dev/null +++ b/crates/cpp/tests/symmetric_future/future/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "future" +version = "0.1.0" +edition = "2021" + +[dependencies] +futures = "0.3.31" +source = { path = "../source" } +symmetric_stream = { path = "../../../../symmetric_executor/symmetric_stream" } +wit-bindgen = { path = "../../../../symmetric_executor/dummy-bindgen", package = "mini-bindgen" } + +[lib] +crate-type = ["cdylib"] diff --git a/crates/cpp/tests/symmetric_future/future/build.rs b/crates/cpp/tests/symmetric_future/future/build.rs new file mode 100644 index 000000000..3096c1795 --- /dev/null +++ b/crates/cpp/tests/symmetric_future/future/build.rs @@ -0,0 +1,9 @@ +use std::env; + +fn main() { + let out = env::var_os("OUT_DIR").unwrap(); + println!( + r"cargo:rustc-link-search={}/../../../deps", + out.into_string().unwrap() + ); +} diff --git a/crates/cpp/tests/symmetric_future/future/src/future_world.rs b/crates/cpp/tests/symmetric_future/future/src/future_world.rs new file mode 100644 index 000000000..f990de5a5 --- /dev/null +++ b/crates/cpp/tests/symmetric_future/future/src/future_world.rs @@ -0,0 +1,235 @@ +// Generated by `wit-bindgen` 0.42.1. DO NOT EDIT! +// Options used: +#[rustfmt::skip] +#[allow(dead_code, clippy::all)] +pub mod test { + pub mod test { + #[allow(dead_code, async_fn_in_trait, unused_imports, clippy::all)] + pub mod future_source { + #[used] + #[doc(hidden)] + static __FORCE_SECTION_REF: fn() = super::super::super::__link_custom_section_describing_imports; + use super::super::super::_rt; + #[allow(unused_unsafe, clippy::all)] + #[allow(async_fn_in_trait)] + pub fn create() -> wit_bindgen::rt::async_support::FutureReader { + unsafe { + #[link(name = "source")] + #[link(wasm_import_module = "test:test/future-source")] + unsafe extern "C" { + #[allow(non_snake_case)] + #[cfg_attr(target_arch = "wasm32", link_name = "create")] + fn testX3AtestX2Ffuture_sourceX00create() -> *mut u8; + } + let ret = testX3AtestX2Ffuture_sourceX00create(); + wit_bindgen::rt::async_support::FutureReader::new( + ret, + &super::super::super::wit_future::vtable0::VTABLE, + ) + } + } + } + } +} +#[rustfmt::skip] +#[allow(dead_code, clippy::all)] +pub mod exports { + pub mod test { + pub mod test { + #[allow(dead_code, async_fn_in_trait, unused_imports, clippy::all)] + pub mod future_test { + #[used] + #[doc(hidden)] + static __FORCE_SECTION_REF: fn() = super::super::super::super::__link_custom_section_describing_imports; + use super::super::super::super::_rt; + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_create_cabi() -> *mut u8 { + unsafe { + #[cfg(target_arch = "wasm32")] _rt::run_ctors_once(); + let result0 = { T::create() }; + (result0).take_handle() as *mut u8 + } + } + pub trait Guest { + #[allow(async_fn_in_trait)] + fn create() -> wit_bindgen::rt::async_support::FutureReader; + } + #[doc(hidden)] + macro_rules! __export_test_test_future_test_cabi { + ($ty:ident with_types_in $($path_to_types:tt)*) => { + const _ : () = { #[cfg_attr(target_arch = "wasm32", export_name = + "create")] #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + #[allow(non_snake_case)] unsafe extern "C" fn + testX3AtestX2Ffuture_testX00create() -> * mut u8 { unsafe { + $($path_to_types)*:: _export_create_cabi::<$ty > () } } }; + }; + } + #[doc(hidden)] + pub(crate) use __export_test_test_future_test_cabi; + } + } + } +} +#[rustfmt::skip] +mod _rt { + #![allow(dead_code, clippy::all)] + pub fn as_i32(t: T) -> i32 { + t.as_i32() + } + pub trait AsI32 { + fn as_i32(self) -> i32; + } + impl<'a, T: Copy + AsI32> AsI32 for &'a T { + fn as_i32(self) -> i32 { + (*self).as_i32() + } + } + impl AsI32 for i32 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + impl AsI32 for u32 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + impl AsI32 for i16 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + impl AsI32 for u16 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + impl AsI32 for i8 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + impl AsI32 for u8 { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + impl AsI32 for char { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + impl AsI32 for usize { + #[inline] + fn as_i32(self) -> i32 { + self as i32 + } + } + #[cfg(target_arch = "wasm32")] + pub fn run_ctors_once() { + wit_bindgen::rt::run_ctors_once(); + } +} +pub mod wit_future { + #![allow(dead_code, unused_variables, clippy::all)] + #[doc(hidden)] + pub trait FuturePayload: Unpin + Sized + 'static { + const VTABLE: &'static wit_bindgen::rt::async_support::FutureVtable; + } + #[doc(hidden)] + #[allow(unused_unsafe)] + pub mod vtable0 { + unsafe fn lift(ptr: *mut u8) -> u32 { + unsafe { + let l0 = *ptr.add(0).cast::(); + l0 as u32 + } + } + unsafe fn lower(value: u32, ptr: *mut u8) { + unsafe { + *ptr.add(0).cast::() = super::super::_rt::as_i32(value); + } + } + unsafe fn dealloc_lists(ptr: *mut u8) { + unsafe {} + } + pub static VTABLE: wit_bindgen::rt::async_support::FutureVtable = + wit_bindgen::rt::async_support::FutureVtable:: { + layout: unsafe { ::std::alloc::Layout::from_size_align_unchecked(4, 4) }, + lift, + lower, + }; + impl super::FuturePayload for u32 { + const VTABLE: &'static wit_bindgen::rt::async_support::FutureVtable = &VTABLE; + } + } + /// Creates a new Component Model `future` with the specified payload type. + /// + /// The `default` function provided computes the default value to be sent in + /// this future if no other value was otherwise sent. + pub fn new( + default: fn() -> T, + ) -> ( + wit_bindgen::rt::async_support::FutureWriter, + wit_bindgen::rt::async_support::FutureReader, + ) { + unsafe { wit_bindgen::rt::async_support::future_new::(default, T::VTABLE) } + } +} +/// Generates `#[unsafe(no_mangle)]` functions to export the specified type as +/// the root implementation of all generated traits. +/// +/// For more information see the documentation of `wit_bindgen::generate!`. +/// +/// ```rust +/// # macro_rules! export{ ($($t:tt)*) => (); } +/// # trait Guest {} +/// struct MyType; +/// +/// impl Guest for MyType { +/// // ... +/// } +/// +/// export!(MyType); +/// ``` +#[allow(unused_macros)] +#[doc(hidden)] +macro_rules! __export_future_world_impl { + ($ty:ident) => { + self::export!($ty with_types_in self); + }; + ($ty:ident with_types_in $($path_to_types_root:tt)*) => { + $($path_to_types_root)*:: + exports::test::test::future_test::__export_test_test_future_test_cabi!($ty + with_types_in $($path_to_types_root)*:: exports::test::test::future_test); + }; +} +#[doc(inline)] +pub(crate) use __export_future_world_impl as export; +#[rustfmt::skip] +#[cfg(target_arch = "wasm32")] +#[unsafe( + link_section = "component-type:wit-bindgen:0.42.1:test:test:future-world:encoded world" +)] +#[doc(hidden)] +#[allow(clippy::octal_escapes)] +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 264] = *b"\ +\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\x85\x01\x01A\x02\x01\ +A\x04\x01B\x03\x01e\x01y\x01@\0\0\0\x04\0\x06create\x01\x01\x03\0\x17test:test/f\ +uture-source\x05\0\x01B\x03\x01e\x01y\x01@\0\0\0\x04\0\x06create\x01\x01\x04\0\x15\ +test:test/future-test\x05\x01\x04\0\x16test:test/future-world\x04\0\x0b\x12\x01\0\ +\x0cfuture-world\x03\0\0\0G\x09producers\x01\x0cprocessed-by\x02\x0dwit-componen\ +t\x070.235.0\x10wit-bindgen-rust\x060.42.1"; +#[inline(never)] +#[doc(hidden)] +pub fn __link_custom_section_describing_imports() { + wit_bindgen::rt::maybe_link_cabi_realloc(); +} diff --git a/crates/cpp/tests/symmetric_future/future/src/lib.rs b/crates/cpp/tests/symmetric_future/future/src/lib.rs new file mode 100644 index 000000000..ea39b0c90 --- /dev/null +++ b/crates/cpp/tests/symmetric_future/future/src/lib.rs @@ -0,0 +1,20 @@ +mod future_world; + +use future_world::test::test::future_source; +use wit_bindgen::rt::async_support; + +future_world::export!(MyStruct with_types_in future_world); + +struct MyStruct; + +impl future_world::exports::test::test::future_test::Guest for MyStruct { + fn create() -> async_support::FutureReader { + let (write, read) = future_world::wit_future::new(u32::default); + let input = future_source::create(); + async_support::spawn(async move { + let input = input.await; + let _ = write.write(input * 2).await; + }); + read + } +} diff --git a/crates/cpp/tests/symmetric_future/future_cpp/Makefile b/crates/cpp/tests/symmetric_future/future_cpp/Makefile new file mode 100644 index 000000000..4c413a300 --- /dev/null +++ b/crates/cpp/tests/symmetric_future/future_cpp/Makefile @@ -0,0 +1,13 @@ +CXXFLAGS=-g -I. -fPIC -I../../../../symmetric_executor/cpp-client -I../../../helper-types +LDFLAGS=-L../../../../symmetric_executor/cpp-client \ + -L../../../../symmetric_executor/target/debug \ + -L../target/debug/deps + +libfuture.so: future_world.o future.o + $(CXX) -shared -o $@ $^ $(LDFLAGS) -lruntime -lsource -lsymmetric_executor -lsymmetric_stream + +clean: + -rm libfuture.so future_world.o future.o + +run: libfuture.so + LD_LIBRARY_PATH=.:../target/debug/deps ../target/debug/main diff --git a/crates/cpp/tests/symmetric_future/future_cpp/future.cpp b/crates/cpp/tests/symmetric_future/future_cpp/future.cpp new file mode 100644 index 000000000..2c1cd0026 --- /dev/null +++ b/crates/cpp/tests/symmetric_future/future_cpp/future.cpp @@ -0,0 +1,9 @@ +#include "future_world_cpp.h" +#include + +std::future exports::test::test::future_test::Create() { + return std::async(std::launch::async, [](){ + auto value = ::test::test::future_source::Create(); + return value.get() * 2; + }); +} diff --git a/crates/cpp/tests/symmetric_future/future_cpp/future_world.cpp b/crates/cpp/tests/symmetric_future/future_cpp/future_world.cpp new file mode 100644 index 000000000..5593ed3e5 --- /dev/null +++ b/crates/cpp/tests/symmetric_future/future_cpp/future_world.cpp @@ -0,0 +1,53 @@ +// Generated by `wit-bindgen` 0.42.1. DO NOT EDIT! + +// Ensure that the *_component_type.o object is linked in +#ifdef __wasm32__ +extern void __component_type_object_force_link_future_world(void); +void __component_type_object_force_link_future_world_public_use_in_this_compilation_unit(void) { + __component_type_object_force_link_future_world(); +} +#endif +#include "future_world_cpp.h" +#include // realloc + +extern "C" void *cabi_realloc(void *ptr, size_t old_size, size_t align, size_t new_size); + +__attribute__((__weak__)) +void *cabi_realloc(void *ptr, size_t old_size, size_t align, size_t new_size) { + (void) old_size; + if (new_size == 0) return (void*) align; + void *ret = realloc(ptr, new_size); + if (!ret) abort(); + return ret; +} + + +struct Lift1 { + static uint32_t lift(uint8_t const* ptr) { int32_t l0 = *((int32_t const*)(ptr + 0)); + return (uint32_t(l0));} + static void lower(uint32_t && value, uint8_t *ptr) { *((int32_t*)(ptr + 0)) = (int32_t(value)); +} +static constexpr size_t SIZE = 4; +}; +struct Lift2 { + static uint32_t lift(uint8_t const* ptr) { int32_t l0 = *((int32_t const*)(ptr + 0)); + return (uint32_t(l0));} + static void lower(uint32_t && value, uint8_t *ptr) { *((int32_t*)(ptr + 0)) = (int32_t(value)); +} +static constexpr size_t SIZE = 4; +}; +#include "async_support.h" +extern "C" uint8_t* testX3AtestX2Ffuture_sourceX00create(); +std::future test::test::future_source::Create() +{ + auto ret = testX3AtestX2Ffuture_sourceX00create(); + return lift_future(ret); +} +extern "C" +uint8_t* testX3AtestX2Ffuture_testX00create() +{ + auto result0 = exports::test::test::future_test::Create(); + return lower_future(std::move(result0)); +} + +// Component Adapters diff --git a/crates/cpp/tests/symmetric_future/future_cpp/future_world_cpp.h b/crates/cpp/tests/symmetric_future/future_cpp/future_world_cpp.h new file mode 100644 index 000000000..14c211dc7 --- /dev/null +++ b/crates/cpp/tests/symmetric_future/future_cpp/future_world_cpp.h @@ -0,0 +1,14 @@ +// Generated by `wit-bindgen` 0.42.1. DO NOT EDIT! +#ifndef __CPP_GUEST_BINDINGS_FUTURE_WORLD_H +#define __CPP_GUEST_BINDINGS_FUTURE_WORLD_H +#define WIT_SYMMETRIC +#include +#include +#include +namespace test {namespace test {namespace future_source {std::future Create(); +// export_interface Interface(Id { idx: 1 }) +}}} +namespace exports {namespace test {namespace test {namespace future_test {std::future Create(); +}}}} + +#endif diff --git a/crates/cpp/tests/symmetric_future/generate.sh b/crates/cpp/tests/symmetric_future/generate.sh new file mode 100755 index 000000000..5d7ac9a5a --- /dev/null +++ b/crates/cpp/tests/symmetric_future/generate.sh @@ -0,0 +1,4 @@ +#!/bin/sh +(cd future/src ; ../../../../../../target/debug/wit-bindgen rust ../../wit/future.wit --symmetric --link-name source --format) +(cd future_cpp; ../../../../../target/debug/wit-bindgen cpp ../wit/future.wit --symmetric) +cargo fmt diff --git a/crates/cpp/tests/symmetric_future/main/Cargo.toml b/crates/cpp/tests/symmetric_future/main/Cargo.toml new file mode 100644 index 000000000..4538ef8fa --- /dev/null +++ b/crates/cpp/tests/symmetric_future/main/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "main" +version = "0.1.0" +edition = "2021" + +[dependencies] +future = { path = "../future" } +symmetric_executor = { path = "../../../../symmetric_executor", features = ["trace"]} +symmetric_stream = { path = "../../../../symmetric_executor/symmetric_stream", features = ["trace"]} +wit-bindgen-symmetric-rt = { path = "../../../../symmetric_executor/rust-client" } diff --git a/crates/cpp/tests/symmetric_future/main/build.rs b/crates/cpp/tests/symmetric_future/main/build.rs new file mode 100644 index 000000000..3096c1795 --- /dev/null +++ b/crates/cpp/tests/symmetric_future/main/build.rs @@ -0,0 +1,9 @@ +use std::env; + +fn main() { + let out = env::var_os("OUT_DIR").unwrap(); + println!( + r"cargo:rustc-link-search={}/../../../deps", + out.into_string().unwrap() + ); +} diff --git a/crates/cpp/tests/symmetric_future/main/src/main.rs b/crates/cpp/tests/symmetric_future/main/src/main.rs new file mode 100644 index 000000000..55846d1f7 --- /dev/null +++ b/crates/cpp/tests/symmetric_future/main/src/main.rs @@ -0,0 +1,52 @@ +use wit_bindgen_symmetric_rt::{ + async_support::Stream, + symmetric_stream::{Address, Buffer}, + CallbackState, +}; + +#[link(name = "future")] +extern "C" { + pub fn testX3AtestX2Ffuture_testX00create() -> usize; +} + +struct CallbackInfo { + stream: Stream, + data: u32, +} + +extern "C" fn ready(arg: *mut ()) -> CallbackState { + let info = unsafe { &*arg.cast::() }; + let buffer = info.stream.read_result(); + if let Some(buffer) = buffer { + let len = buffer.get_size(); + if len > 0 { + println!("data {}", info.data); + } + } + // finished + CallbackState::Ready +} + +fn main() { + let result_future = unsafe { testX3AtestX2Ffuture_testX00create() }; + // function should have completed (not async) + // assert!(continuation.is_null()); + let stream = unsafe { Stream::from_handle(result_future) }; + let mut info = Box::pin(CallbackInfo { + stream: stream.clone(), + data: 0, + }); + let buffer = Buffer::new( + unsafe { Address::from_handle(&mut info.data as *mut u32 as usize) }, + 1, + ); + stream.start_reading(buffer); + let subscription = stream.read_ready_subscribe(); + println!("Register read in main"); + wit_bindgen_symmetric_rt::register( + subscription, + ready, + (&*info as *const CallbackInfo).cast_mut().cast(), + ); + wit_bindgen_symmetric_rt::run(); +} diff --git a/crates/cpp/tests/symmetric_future/source/Cargo.toml b/crates/cpp/tests/symmetric_future/source/Cargo.toml new file mode 100644 index 000000000..995658bad --- /dev/null +++ b/crates/cpp/tests/symmetric_future/source/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "source" +version = "0.1.0" +edition = "2021" + +[dependencies] +symmetric_executor = { path = "../../../../symmetric_executor" } +symmetric_stream = { path = "../../../../symmetric_executor/symmetric_stream" } +wit-bindgen-symmetric-rt = { path = "../../../../symmetric_executor/rust-client" } + +[lib] +crate-type = ["cdylib"] diff --git a/crates/cpp/tests/symmetric_future/source/build.rs b/crates/cpp/tests/symmetric_future/source/build.rs new file mode 100644 index 000000000..3096c1795 --- /dev/null +++ b/crates/cpp/tests/symmetric_future/source/build.rs @@ -0,0 +1,9 @@ +use std::env; + +fn main() { + let out = env::var_os("OUT_DIR").unwrap(); + println!( + r"cargo:rustc-link-search={}/../../../deps", + out.into_string().unwrap() + ); +} diff --git a/crates/cpp/tests/symmetric_future/source/src/lib.rs b/crates/cpp/tests/symmetric_future/source/src/lib.rs new file mode 100644 index 000000000..832dcb40f --- /dev/null +++ b/crates/cpp/tests/symmetric_future/source/src/lib.rs @@ -0,0 +1,31 @@ +use wit_bindgen_symmetric_rt::{async_support::Stream, register, CallbackState, EventSubscription}; + +extern "C" fn timer_call(data: *mut ()) -> CallbackState { + let stream: Stream = unsafe { Stream::from_handle(data as usize) }; + let buffer = stream.start_writing(); + let addr = buffer.get_address().take_handle() as *mut u32; + let size = buffer.capacity(); + assert!(size >= 1); + *unsafe { &mut *addr } = 21; + buffer.set_size(1); + stream.finish_writing(Some(buffer)); + // let _ = stream.take_handle(); + CallbackState::Ready +} + +extern "C" fn write_ready(data: *mut ()) -> CallbackState { + println!("we can write now, starting timer"); + let ms_30 = EventSubscription::from_timeout(30 * 1_000_000); + register(ms_30, timer_call, data); + CallbackState::Ready +} + +#[allow(non_snake_case)] +#[no_mangle] +pub fn testX3AtestX2Ffuture_sourceX00create() -> usize { + let stream = Stream::new(); + let event = stream.write_ready_subscribe(); + let stream_copy = stream.clone(); + register(event, write_ready, stream_copy.take_handle() as *mut ()); + stream.take_handle() +} diff --git a/crates/cpp/tests/symmetric_future/wit/future.wit b/crates/cpp/tests/symmetric_future/wit/future.wit new file mode 100644 index 000000000..d59ccbb68 --- /dev/null +++ b/crates/cpp/tests/symmetric_future/wit/future.wit @@ -0,0 +1,14 @@ +package test:test; + +interface future-source { + create: func() -> future; +} + +interface future-test { + create: func() -> future; +} + +world future-world { + import future-source; + export future-test; +} diff --git a/crates/cpp/tests/symmetric_future_string/Cargo.lock b/crates/cpp/tests/symmetric_future_string/Cargo.lock new file mode 100644 index 000000000..f7f169f23 --- /dev/null +++ b/crates/cpp/tests/symmetric_future_string/Cargo.lock @@ -0,0 +1,221 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "dummy-rt" +version = "0.1.0" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mini-bindgen" +version = "0.1.0" +dependencies = [ + "dummy-rt", + "wit-bindgen-symmetric-rt", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "stream-string" +version = "0.1.0" +dependencies = [ + "mini-bindgen", + "symmetric_executor", + "symmetric_stream", + "wit-bindgen-symmetric-rt", +] + +[[package]] +name = "symmetric_executor" +version = "0.1.0" +dependencies = [ + "dummy-rt", + "futures", + "libc", +] + +[[package]] +name = "symmetric_stream" +version = "0.1.0" +dependencies = [ + "dummy-rt", + "symmetric_executor", + "wit-bindgen-symmetric-rt", +] + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "wit-bindgen-symmetric-rt" +version = "0.36.0" +dependencies = [ + "bitflags", + "dummy-rt", + "futures", +] diff --git a/crates/cpp/tests/symmetric_future_string/Cargo.toml b/crates/cpp/tests/symmetric_future_string/Cargo.toml new file mode 100644 index 000000000..3e7228443 --- /dev/null +++ b/crates/cpp/tests/symmetric_future_string/Cargo.toml @@ -0,0 +1,12 @@ +[workspace] + +[package] +name = "stream-string" +version = "0.1.0" +edition = "2021" + +[dependencies] +symmetric_executor = { path = "../../../symmetric_executor", features = ["trace"]} +symmetric_stream = { path = "../../../symmetric_executor/symmetric_stream" } +wit-bindgen-symmetric-rt = { path = "../../../symmetric_executor/rust-client" } +wit-bindgen = { path = "../../../symmetric_executor/dummy-bindgen", package = "mini-bindgen" } diff --git a/crates/cpp/tests/symmetric_future_string/bindings/runner.rs b/crates/cpp/tests/symmetric_future_string/bindings/runner.rs new file mode 100644 index 000000000..648b91f25 --- /dev/null +++ b/crates/cpp/tests/symmetric_future_string/bindings/runner.rs @@ -0,0 +1,138 @@ +// Generated by `wit-bindgen` 0.42.1. DO NOT EDIT! +// Options used: +#[rustfmt::skip] +#[allow(dead_code, clippy::all)] +pub mod a { + pub mod b { + #[allow(dead_code, async_fn_in_trait, unused_imports, clippy::all)] + pub mod the_test { + #[used] + #[doc(hidden)] + static __FORCE_SECTION_REF: fn() = super::super::super::__link_custom_section_describing_imports; + use super::super::super::_rt; + #[allow(unused_unsafe, clippy::all)] + #[allow(async_fn_in_trait)] + pub fn f() -> wit_bindgen::rt::async_support::FutureReader<_rt::String> { + unsafe { + #[link(wasm_import_module = "a:b/the-test")] + unsafe extern "C" { + #[allow(non_snake_case)] + #[cfg_attr(target_arch = "wasm32", link_name = "f")] + fn aX3AbX2Fthe_testX00f() -> *mut u8; + } + let ret = aX3AbX2Fthe_testX00f(); + wit_bindgen::rt::async_support::FutureReader::new( + ret, + &super::super::super::wit_future::vtable0::VTABLE, + ) + } + } + } + } +} +#[rustfmt::skip] +mod _rt { + #![allow(dead_code, clippy::all)] + pub use alloc_crate::string::String; + pub use alloc_crate::vec::Vec; + pub unsafe fn string_lift(bytes: Vec) -> String { + if cfg!(debug_assertions) { + String::from_utf8(bytes).unwrap() + } else { + unsafe { String::from_utf8_unchecked(bytes) } + } + } + pub unsafe fn cabi_dealloc(ptr: *mut u8, size: usize, align: usize) { + if size == 0 { + return; + } + unsafe { + let layout = alloc::Layout::from_size_align_unchecked(size, align); + alloc::dealloc(ptr, layout); + } + } + extern crate alloc as alloc_crate; + pub use alloc_crate::alloc; +} +pub mod wit_future { + #![allow(dead_code, unused_variables, clippy::all)] + #[doc(hidden)] + pub trait FuturePayload: Unpin + Sized + 'static { + const VTABLE: &'static wit_bindgen::rt::async_support::FutureVtable; + } + #[doc(hidden)] + #[allow(unused_unsafe)] + pub mod vtable0 { + unsafe fn lift(ptr: *mut u8) -> super::super::_rt::String { + unsafe { + let l0 = *ptr.add(0).cast::<*mut u8>(); + let l1 = *ptr.add(::core::mem::size_of::<*const u8>()).cast::(); + let len2 = l1; + let bytes2 = if len2 > 0 { + super::super::_rt::Vec::from_raw_parts(l0.cast(), len2, len2) + } else { + Default::default() + }; + super::super::_rt::string_lift(bytes2) + } + } + unsafe fn lower(value: super::super::_rt::String, ptr: *mut u8) { + unsafe { + let vec0 = value; + let ptr0 = vec0.as_ptr().cast::(); + let len0 = vec0.len(); + *ptr.add(::core::mem::size_of::<*const u8>()).cast::() = len0; + *ptr.add(0).cast::<*mut u8>() = ptr0.cast_mut(); + } + } + unsafe fn dealloc_lists(ptr: *mut u8) { + unsafe { + let l0 = *ptr.add(0).cast::<*mut u8>(); + let l1 = *ptr.add(::core::mem::size_of::<*const u8>()).cast::(); + super::super::_rt::cabi_dealloc(l0, l1, 1); + } + } + pub static VTABLE: wit_bindgen::rt::async_support::FutureVtable< + super::super::_rt::String, + > = wit_bindgen::rt::async_support::FutureVtable:: { + layout: unsafe { + ::std::alloc::Layout::from_size_align_unchecked( + 2 * ::core::mem::size_of::<*const u8>(), + ::core::mem::size_of::<*const u8>(), + ) + }, + lift, + lower, + }; + impl super::FuturePayload for super::super::_rt::String { + const VTABLE: &'static wit_bindgen::rt::async_support::FutureVtable = &VTABLE; + } + } + /// Creates a new Component Model `future` with the specified payload type. + /// + /// The `default` function provided computes the default value to be sent in + /// this future if no other value was otherwise sent. + pub fn new( + default: fn() -> T, + ) -> ( + wit_bindgen::rt::async_support::FutureWriter, + wit_bindgen::rt::async_support::FutureReader, + ) { + unsafe { wit_bindgen::rt::async_support::future_new::(default, T::VTABLE) } + } +} +#[rustfmt::skip] +#[cfg(target_arch = "wasm32")] +#[unsafe(link_section = "component-type:wit-bindgen:0.42.1:a:b:runner:encoded world")] +#[doc(hidden)] +#[allow(clippy::octal_escapes)] +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 180] = *b"\ +\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x078\x01A\x02\x01A\x02\x01\ +B\x03\x01e\x01s\x01@\0\0\0\x04\0\x01f\x01\x01\x03\0\x0ca:b/the-test\x05\0\x04\0\x0a\ +a:b/runner\x04\0\x0b\x0c\x01\0\x06runner\x03\0\0\0G\x09producers\x01\x0cprocesse\ +d-by\x02\x0dwit-component\x070.235.0\x10wit-bindgen-rust\x060.42.1"; +#[inline(never)] +#[doc(hidden)] +pub fn __link_custom_section_describing_imports() { + wit_bindgen::rt::maybe_link_cabi_realloc(); +} diff --git a/crates/cpp/tests/symmetric_future_string/bindings/test.rs b/crates/cpp/tests/symmetric_future_string/bindings/test.rs new file mode 100644 index 000000000..c117e0e14 --- /dev/null +++ b/crates/cpp/tests/symmetric_future_string/bindings/test.rs @@ -0,0 +1,173 @@ +// Generated by `wit-bindgen` 0.42.1. DO NOT EDIT! +// Options used: +#[rustfmt::skip] +#[allow(dead_code, clippy::all)] +pub mod exports { + pub mod a { + pub mod b { + #[allow(dead_code, async_fn_in_trait, unused_imports, clippy::all)] + pub mod the_test { + #[used] + #[doc(hidden)] + static __FORCE_SECTION_REF: fn() = super::super::super::super::__link_custom_section_describing_imports; + use super::super::super::super::_rt; + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_f_cabi() -> *mut u8 { + unsafe { + #[cfg(target_arch = "wasm32")] _rt::run_ctors_once(); + let result0 = { T::f() }; + (result0).take_handle() as *mut u8 + } + } + pub trait Guest { + #[allow(async_fn_in_trait)] + fn f() -> wit_bindgen::rt::async_support::FutureReader<_rt::String>; + } + #[doc(hidden)] + macro_rules! __export_a_b_the_test_cabi { + ($ty:ident with_types_in $($path_to_types:tt)*) => { + const _ : () = { #[cfg_attr(target_arch = "wasm32", export_name = + "f")] #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + #[allow(non_snake_case)] unsafe extern "C" fn + aX3AbX2Fthe_testX00f() -> * mut u8 { unsafe { + $($path_to_types)*:: _export_f_cabi::<$ty > () } } }; + }; + } + #[doc(hidden)] + pub(crate) use __export_a_b_the_test_cabi; + } + } + } +} +#[rustfmt::skip] +mod _rt { + #![allow(dead_code, clippy::all)] + pub use alloc_crate::string::String; + pub unsafe fn cabi_dealloc(ptr: *mut u8, size: usize, align: usize) { + if size == 0 { + return; + } + unsafe { + let layout = alloc::Layout::from_size_align_unchecked(size, align); + alloc::dealloc(ptr, layout); + } + } + #[cfg(target_arch = "wasm32")] + pub fn run_ctors_once() { + wit_bindgen::rt::run_ctors_once(); + } + extern crate alloc as alloc_crate; + pub use alloc_crate::alloc; +} +pub mod wit_future { + #![allow(dead_code, unused_variables, clippy::all)] + #[doc(hidden)] + pub trait FuturePayload: Unpin + Sized + 'static { + const VTABLE: &'static wit_bindgen::rt::async_support::FutureVtable; + } + #[doc(hidden)] + #[allow(unused_unsafe)] + pub mod vtable0 { + unsafe fn lift(ptr: *mut u8) -> super::super::_rt::String { + unsafe { + let l0 = *ptr.add(0).cast::<*mut u8>(); + let l1 = *ptr.add(::core::mem::size_of::<*const u8>()).cast::(); + let len2 = l1; + let string2 = String::from( + std::str::from_utf8(std::slice::from_raw_parts(l0, len2)).unwrap(), + ); + string2 + } + } + unsafe fn lower(value: super::super::_rt::String, ptr: *mut u8) { + unsafe { + let vec0 = (value.into_bytes()).into_boxed_slice(); + let ptr0 = vec0.as_ptr().cast::(); + let len0 = vec0.len(); + ::core::mem::forget(vec0); + *ptr.add(::core::mem::size_of::<*const u8>()).cast::() = len0; + *ptr.add(0).cast::<*mut u8>() = ptr0.cast_mut(); + } + } + unsafe fn dealloc_lists(ptr: *mut u8) { + unsafe { + let l0 = *ptr.add(0).cast::<*mut u8>(); + let l1 = *ptr.add(::core::mem::size_of::<*const u8>()).cast::(); + super::super::_rt::cabi_dealloc(l0, l1, 1); + } + } + pub static VTABLE: wit_bindgen::rt::async_support::FutureVtable< + super::super::_rt::String, + > = wit_bindgen::rt::async_support::FutureVtable:: { + layout: unsafe { + ::std::alloc::Layout::from_size_align_unchecked( + 2 * ::core::mem::size_of::<*const u8>(), + ::core::mem::size_of::<*const u8>(), + ) + }, + lift, + lower, + }; + impl super::FuturePayload for super::super::_rt::String { + const VTABLE: &'static wit_bindgen::rt::async_support::FutureVtable = &VTABLE; + } + } + /// Creates a new Component Model `future` with the specified payload type. + /// + /// The `default` function provided computes the default value to be sent in + /// this future if no other value was otherwise sent. + pub fn new( + default: fn() -> T, + ) -> ( + wit_bindgen::rt::async_support::FutureWriter, + wit_bindgen::rt::async_support::FutureReader, + ) { + unsafe { wit_bindgen::rt::async_support::future_new::(default, T::VTABLE) } + } +} +/// Generates `#[unsafe(no_mangle)]` functions to export the specified type as +/// the root implementation of all generated traits. +/// +/// For more information see the documentation of `wit_bindgen::generate!`. +/// +/// ```rust +/// # macro_rules! export{ ($($t:tt)*) => (); } +/// # trait Guest {} +/// struct MyType; +/// +/// impl Guest for MyType { +/// // ... +/// } +/// +/// export!(MyType); +/// ``` +#[allow(unused_macros)] +#[doc(hidden)] +macro_rules! __export_test_impl { + ($ty:ident) => { + self::export!($ty with_types_in self); + }; + ($ty:ident with_types_in $($path_to_types_root:tt)*) => { + $($path_to_types_root)*:: + exports::a::b::the_test::__export_a_b_the_test_cabi!($ty with_types_in + $($path_to_types_root)*:: exports::a::b::the_test); + }; +} +#[doc(inline)] +pub(crate) use __export_test_impl as export; +#[rustfmt::skip] +#[cfg(target_arch = "wasm32")] +#[unsafe(link_section = "component-type:wit-bindgen:0.42.1:a:b:test:encoded world")] +#[doc(hidden)] +#[allow(clippy::octal_escapes)] +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 176] = *b"\ +\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x076\x01A\x02\x01A\x02\x01\ +B\x03\x01e\x01s\x01@\0\0\0\x04\0\x01f\x01\x01\x04\0\x0ca:b/the-test\x05\0\x04\0\x08\ +a:b/test\x04\0\x0b\x0a\x01\0\x04test\x03\0\0\0G\x09producers\x01\x0cprocessed-by\ +\x02\x0dwit-component\x070.235.0\x10wit-bindgen-rust\x060.42.1"; +#[inline(never)] +#[doc(hidden)] +pub fn __link_custom_section_describing_imports() { + wit_bindgen::rt::maybe_link_cabi_realloc(); +} diff --git a/crates/cpp/tests/symmetric_future_string/build.rs b/crates/cpp/tests/symmetric_future_string/build.rs new file mode 100644 index 000000000..3096c1795 --- /dev/null +++ b/crates/cpp/tests/symmetric_future_string/build.rs @@ -0,0 +1,9 @@ +use std::env; + +fn main() { + let out = env::var_os("OUT_DIR").unwrap(); + println!( + r"cargo:rustc-link-search={}/../../../deps", + out.into_string().unwrap() + ); +} diff --git a/crates/cpp/tests/symmetric_future_string/format.sh b/crates/cpp/tests/symmetric_future_string/format.sh new file mode 100755 index 000000000..e0e74c7c8 --- /dev/null +++ b/crates/cpp/tests/symmetric_future_string/format.sh @@ -0,0 +1,3 @@ +#!/bin/sh +rustfmt --edition=2024 bindings/*.rs +rustfmt --edition=2024 *.rs diff --git a/crates/cpp/tests/symmetric_future_string/generate.sh b/crates/cpp/tests/symmetric_future_string/generate.sh new file mode 100755 index 000000000..727a922a5 --- /dev/null +++ b/crates/cpp/tests/symmetric_future_string/generate.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +cd bindings +../../../../../target/debug/wit-bindgen rust ../test.wit --symmetric -w test --format +../../../../../target/debug/wit-bindgen rust ../test.wit --symmetric -w runner --format diff --git a/crates/cpp/tests/symmetric_future_string/runner.rs b/crates/cpp/tests/symmetric_future_string/runner.rs new file mode 100644 index 000000000..815647e47 --- /dev/null +++ b/crates/cpp/tests/symmetric_future_string/runner.rs @@ -0,0 +1,15 @@ +//@ args = '--async=-none' + +//include!(env!("BINDINGS")); +include!("bindings/runner.rs"); + +use wit_bindgen::rt::async_support; + +use crate::a::b::the_test::f; + +fn main() { + async_support::block_on(async { + let result = f().await; + assert_eq!(result, String::from("Hello")); + }); +} diff --git a/crates/cpp/tests/symmetric_future_string/src/main.rs b/crates/cpp/tests/symmetric_future_string/src/main.rs new file mode 100644 index 000000000..8eac55a1f --- /dev/null +++ b/crates/cpp/tests/symmetric_future_string/src/main.rs @@ -0,0 +1,3 @@ +mod test; + +include!("../runner.rs"); diff --git a/crates/cpp/tests/symmetric_future_string/src/test.rs b/crates/cpp/tests/symmetric_future_string/src/test.rs new file mode 100644 index 000000000..b61c677fc --- /dev/null +++ b/crates/cpp/tests/symmetric_future_string/src/test.rs @@ -0,0 +1 @@ +include!("../test.rs"); diff --git a/crates/cpp/tests/symmetric_future_string/test.rs b/crates/cpp/tests/symmetric_future_string/test.rs new file mode 100644 index 000000000..c591bc930 --- /dev/null +++ b/crates/cpp/tests/symmetric_future_string/test.rs @@ -0,0 +1,20 @@ +// include!(env!("BINDINGS")); +include!("bindings/test.rs"); + +struct Component; + +export!(Component); + +use exports::a::b::the_test::Guest; + +use wit_bindgen::rt::async_support::{self, FutureReader}; + +impl Guest for Component { + fn f() -> FutureReader { + let (wr, rd) = wit_future::new(String::default); + async_support::spawn(async move { + wr.write(String::from("Hello")).await; + }); + rd + } +} diff --git a/crates/cpp/tests/symmetric_future_string/test.wit b/crates/cpp/tests/symmetric_future_string/test.wit new file mode 100644 index 000000000..174f61646 --- /dev/null +++ b/crates/cpp/tests/symmetric_future_string/test.wit @@ -0,0 +1,12 @@ +package a:b; + +interface the-test { + f: func() -> future; +} + +world test { + export the-test; +} +world runner { + import the-test; +} diff --git a/crates/cpp/tests/symmetric_lists/rust_a/Cargo.toml b/crates/cpp/tests/symmetric_lists/rust_a/Cargo.toml new file mode 100644 index 000000000..d4aa7a836 --- /dev/null +++ b/crates/cpp/tests/symmetric_lists/rust_a/Cargo.toml @@ -0,0 +1,10 @@ +[workspace] + +[package] +name = "rust_a" +version = "0.1.0" +edition = "2021" + +[dependencies] +rust_b = { path = "../rust_b" } +wit-bindgen = { path = "../../../../guest-rust" } diff --git a/crates/cpp/tests/symmetric_lists/rust_a/Makefile b/crates/cpp/tests/symmetric_lists/rust_a/Makefile new file mode 100644 index 000000000..196b4dd88 --- /dev/null +++ b/crates/cpp/tests/symmetric_lists/rust_a/Makefile @@ -0,0 +1,3 @@ + +bindgen: + (cd src; ../../../../../../target/debug/wit-bindgen rust ../../wit/lists.wit -w x --generate-all --symmetric) diff --git a/crates/cpp/tests/symmetric_lists/rust_a/src/main.rs b/crates/cpp/tests/symmetric_lists/rust_a/src/main.rs new file mode 100644 index 000000000..4f5f0a6b8 --- /dev/null +++ b/crates/cpp/tests/symmetric_lists/rust_a/src/main.rs @@ -0,0 +1,22 @@ +// compile with: RUSTFLAGS=-L../rust_b/target/debug cargo build + +mod x; + +// force linking to librust_b.so +#[allow(dead_code)] +fn b() { + #[link(name = "rust_b")] + extern "C" { + fn testX3AtestX2FiX00f(_: *mut u8, _: usize, _: *mut u8); + } + unsafe { testX3AtestX2FiX00f(core::ptr::null_mut(), 0, core::ptr::null_mut()) }; +} + +fn main() { + let input = vec!["hello".into(), "world".into()]; + let output = x::test::test::i::f(&input); + println!("{output:?}"); + let input2 = vec![1,2,3]; + let output2 = x::test::test::i::g(&input2); + println!("{output2:?}"); +} diff --git a/crates/cpp/tests/symmetric_lists/rust_a/src/x.rs b/crates/cpp/tests/symmetric_lists/rust_a/src/x.rs new file mode 100644 index 000000000..bd8902872 --- /dev/null +++ b/crates/cpp/tests/symmetric_lists/rust_a/src/x.rs @@ -0,0 +1,153 @@ +// Generated by `wit-bindgen` 0.30.0. DO NOT EDIT! +// Options used: +#[allow(dead_code)] +pub mod test { + #[allow(dead_code)] + pub mod test { + #[allow(dead_code, clippy::all)] + pub mod i { + #[used] + #[doc(hidden)] + static __FORCE_SECTION_REF: fn() = + super::super::super::__link_custom_section_describing_imports; + + use super::super::super::_rt; + #[allow(unused_unsafe, clippy::all)] + pub fn f(a: &[_rt::String]) -> _rt::Vec<_rt::String> { + unsafe { + #[cfg_attr(target_pointer_width = "64", repr(align(8)))] + #[cfg_attr(target_pointer_width = "32", repr(align(4)))] + struct RetArea( + [::core::mem::MaybeUninit; 2 * core::mem::size_of::<*const u8>()], + ); + let mut ret_area = RetArea( + [::core::mem::MaybeUninit::uninit(); 2 * core::mem::size_of::<*const u8>()], + ); + let vec1 = a; + let len1 = vec1.len(); + let layout1 = _rt::alloc::Layout::from_size_align_unchecked( + vec1.len() * (2 * core::mem::size_of::<*const u8>()), + core::mem::size_of::<*const u8>(), + ); + let result1 = if layout1.size() != 0 { + let ptr = _rt::alloc::alloc(layout1).cast::(); + if ptr.is_null() { + _rt::alloc::handle_alloc_error(layout1); + } + ptr + } else { + ::core::ptr::null_mut() + }; + for (i, e) in vec1.into_iter().enumerate() { + let base = result1.add(i * (2 * core::mem::size_of::<*const u8>())); + { + let vec0 = e; + let ptr0 = vec0.as_ptr().cast::(); + let len0 = vec0.len(); + *base.add(core::mem::size_of::<*const u8>()).cast::() = len0; + *base.add(0).cast::<*mut u8>() = ptr0.cast_mut(); + } + } + let ptr2 = ret_area.0.as_mut_ptr().cast::(); + #[link(wasm_import_module = "test:test/i")] + extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "f")] + fn testX3AtestX2FiX00f(_: *mut u8, _: usize, _: *mut u8); + } + testX3AtestX2FiX00f(result1, len1, ptr2); + _rt::alloc::dealloc(result1, layout1); + let l3 = *ptr2.add(0).cast::<*mut u8>(); + let l4 = *ptr2.add(core::mem::size_of::<*const u8>()).cast::(); + let base8 = l3; + let len8 = l4; + let mut result8 = _rt::Vec::with_capacity(len8); + for i in 0..len8 { + let base = base8.add(i * (2 * core::mem::size_of::<*const u8>())); + let e8 = { + let l5 = *base.add(0).cast::<*mut u8>(); + let l6 = *base.add(core::mem::size_of::<*const u8>()).cast::(); + let len7 = l6; + let bytes7 = if len7 > 0 { + _rt::Vec::from_raw_parts(l5.cast(), len7, len7) + } else { + Default::default() + }; + + _rt::string_lift(bytes7) + }; + result8.push(e8); + } + _rt::cabi_dealloc( + base8, + len8 * (2 * core::mem::size_of::<*const u8>()), + core::mem::size_of::<*const u8>(), + ); + result8 + } + } + #[allow(unused_unsafe, clippy::all)] + pub fn g(a: &[u8]) -> _rt::Vec { + unsafe { + #[cfg_attr(target_pointer_width = "64", repr(align(8)))] + #[cfg_attr(target_pointer_width = "32", repr(align(4)))] + struct RetArea( + [::core::mem::MaybeUninit; 2 * core::mem::size_of::<*const u8>()], + ); + let mut ret_area = RetArea( + [::core::mem::MaybeUninit::uninit(); 2 * core::mem::size_of::<*const u8>()], + ); + let vec0 = a; + let ptr0 = vec0.as_ptr().cast::(); + let len0 = vec0.len(); + let ptr1 = ret_area.0.as_mut_ptr().cast::(); + #[link(wasm_import_module = "test:test/i")] + extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "g")] + fn testX3AtestX2FiX00g(_: *mut u8, _: usize, _: *mut u8); + } + testX3AtestX2FiX00g(ptr0.cast_mut(), len0, ptr1); + let l2 = *ptr1.add(0).cast::<*mut u8>(); + let l3 = *ptr1.add(core::mem::size_of::<*const u8>()).cast::(); + let len4 = l3; + _rt::Vec::from_raw_parts(l2.cast(), len4, len4) + } + } + } + } +} +mod _rt { + pub use alloc_crate::alloc; + pub use alloc_crate::string::String; + pub use alloc_crate::vec::Vec; + pub unsafe fn string_lift(bytes: Vec) -> String { + if cfg!(debug_assertions) { + String::from_utf8(bytes).unwrap() + } else { + String::from_utf8_unchecked(bytes) + } + } + pub unsafe fn cabi_dealloc(ptr: *mut u8, size: usize, align: usize) { + if size == 0 { + return; + } + let layout = alloc::Layout::from_size_align_unchecked(size, align); + alloc::dealloc(ptr, layout); + } + extern crate alloc as alloc_crate; +} + +#[cfg(target_arch = "wasm32")] +#[link_section = "component-type:wit-bindgen:0.30.0:test:test:x:encoded world"] +#[doc(hidden)] +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 194] = *b"\ +\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07K\x01A\x02\x01A\x02\x01\ +B\x06\x01ps\x01@\x01\x01a\0\0\0\x04\0\x01f\x01\x01\x01p}\x01@\x01\x01a\x02\0\x02\ +\x04\0\x01g\x01\x03\x03\x01\x0btest:test/i\x05\0\x04\x01\x0btest:test/x\x04\0\x0b\ +\x07\x01\0\x01x\x03\0\0\0G\x09producers\x01\x0cprocessed-by\x02\x0dwit-component\ +\x070.216.0\x10wit-bindgen-rust\x060.30.0"; + +#[inline(never)] +#[doc(hidden)] +pub fn __link_custom_section_describing_imports() { + wit_bindgen::rt::maybe_link_cabi_realloc(); +} diff --git a/crates/cpp/tests/symmetric_lists/rust_b/Cargo.toml b/crates/cpp/tests/symmetric_lists/rust_b/Cargo.toml new file mode 100644 index 000000000..8fd19d382 --- /dev/null +++ b/crates/cpp/tests/symmetric_lists/rust_b/Cargo.toml @@ -0,0 +1,12 @@ +[workspace] + +[package] +name = "rust_b" +version = "0.1.0" +edition = "2021" + +[dependencies] +wit-bindgen = { path = "../../../../guest-rust" } + +[lib] +crate-type = ["cdylib"] diff --git a/crates/cpp/tests/symmetric_lists/rust_b/Makefile b/crates/cpp/tests/symmetric_lists/rust_b/Makefile new file mode 100644 index 000000000..26fd41ee6 --- /dev/null +++ b/crates/cpp/tests/symmetric_lists/rust_b/Makefile @@ -0,0 +1,3 @@ + +bindgen: + (cd src; ../../../../../../target/debug/wit-bindgen rust ../../wit/lists.wit -w w --generate-all --symmetric) diff --git a/crates/cpp/tests/symmetric_lists/rust_b/src/lib.rs b/crates/cpp/tests/symmetric_lists/rust_b/src/lib.rs new file mode 100644 index 000000000..ebecc3949 --- /dev/null +++ b/crates/cpp/tests/symmetric_lists/rust_b/src/lib.rs @@ -0,0 +1,15 @@ +mod w; + +struct MyImpl; + +impl w::exports::test::test::i::Guest for MyImpl { + fn f(a: Vec) -> Vec { + a + } + + fn g(a: Vec) -> Vec { + a + } +} + +w::export!(MyImpl with_types_in w); diff --git a/crates/cpp/tests/symmetric_lists/rust_b/src/w.rs b/crates/cpp/tests/symmetric_lists/rust_b/src/w.rs new file mode 100644 index 000000000..e660ea1d6 --- /dev/null +++ b/crates/cpp/tests/symmetric_lists/rust_b/src/w.rs @@ -0,0 +1,156 @@ +#[allow(dead_code)] +pub mod exports { + #[allow(dead_code)] + pub mod test { + #[allow(dead_code)] + pub mod test { + #[allow(dead_code, clippy::all)] + pub mod i { + #[used] + #[doc(hidden)] + static __FORCE_SECTION_REF: fn() = + super::super::super::super::__link_custom_section_describing_imports; + use super::super::super::super::_rt; + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_f_cabi(arg0: *mut u8, arg1: usize, arg2: *mut u8) { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let base3 = arg0; + let len3 = arg1; + let mut result3 = _rt::Vec::with_capacity(len3); + for i in 0..len3 { + let base = base3.add(i * (2 * core::mem::size_of::<*const u8>())); + let e3 = { + let l0 = *base.add(0).cast::<*mut u8>(); + let l1 = *base.add(core::mem::size_of::<*const u8>()).cast::(); + let len2 = l1; + let string2 = String::from( + std::str::from_utf8(std::slice::from_raw_parts(l0, len2)).unwrap(), + ); + string2 + }; + result3.push(e3); + } + let result4 = T::f(result3); + let vec6 = result4; + let len6 = vec6.len(); + let layout6 = _rt::alloc::Layout::from_size_align_unchecked( + vec6.len() * (2 * core::mem::size_of::<*const u8>()), + core::mem::size_of::<*const u8>(), + ); + let result6 = if layout6.size() != 0 { + let ptr = _rt::alloc::alloc(layout6).cast::(); + if ptr.is_null() { + _rt::alloc::handle_alloc_error(layout6); + } + ptr + } else { + ::core::ptr::null_mut() + }; + for (i, e) in vec6.into_iter().enumerate() { + let base = result6.add(i * (2 * core::mem::size_of::<*const u8>())); + { + let vec5 = (e.into_bytes()).into_boxed_slice(); + let ptr5 = vec5.as_ptr().cast::(); + let len5 = vec5.len(); + ::core::mem::forget(vec5); + *base.add(core::mem::size_of::<*const u8>()).cast::() = len5; + *base.add(0).cast::<*mut u8>() = ptr5.cast_mut(); + } + } + *arg2.add(core::mem::size_of::<*const u8>()).cast::() = len6; + *arg2.add(0).cast::<*mut u8>() = result6; + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_g_cabi(arg0: *mut u8, arg1: usize, arg2: *mut u8) { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let len0 = arg1; + let result1 = + T::g(unsafe { std::slice::from_raw_parts(arg0.cast(), len0) }.to_vec()); + let vec2 = (result1).into_boxed_slice(); + let ptr2 = vec2.as_ptr().cast::(); + let len2 = vec2.len(); + ::core::mem::forget(vec2); + *arg2.add(core::mem::size_of::<*const u8>()).cast::() = len2; + *arg2.add(0).cast::<*mut u8>() = ptr2.cast_mut(); + } + pub trait Guest { + fn f(a: _rt::Vec<_rt::String>) -> _rt::Vec<_rt::String>; + fn g(a: _rt::Vec) -> _rt::Vec; + } + #[doc(hidden)] + macro_rules! __export_test_test_i_cabi { + ($ty:ident with_types_in $($path_to_types:tt)*) => { + const _ : () = { #[cfg_attr(target_arch = "wasm32", export_name = + "f")] #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] unsafe + extern "C" fn testX3AtestX2FiX00f(arg0 : * mut u8, arg1 : usize, + arg2 : * mut u8,) { $($path_to_types)*:: _export_f_cabi::<$ty > + (arg0, arg1, arg2) } #[cfg_attr(target_arch = "wasm32", + export_name = "g")] #[cfg_attr(not(target_arch = "wasm32"), + no_mangle)] unsafe extern "C" fn testX3AtestX2FiX00g(arg0 : * mut + u8, arg1 : usize, arg2 : * mut u8,) { $($path_to_types)*:: + _export_g_cabi::<$ty > (arg0, arg1, arg2) } }; + }; + } + #[doc(hidden)] + pub(crate) use __export_test_test_i_cabi; + } + } + } +} +mod _rt { + #[cfg(target_arch = "wasm32")] + pub fn run_ctors_once() { + wit_bindgen::rt::run_ctors_once(); + } + pub use alloc_crate::alloc; + pub use alloc_crate::string::String; + pub use alloc_crate::vec::Vec; + extern crate alloc as alloc_crate; +} +/// Generates `#[no_mangle]` functions to export the specified type as the +/// root implementation of all generated traits. +/// +/// For more information see the documentation of `wit_bindgen::generate!`. +/// +/// ```rust +/// # macro_rules! export{ ($($t:tt)*) => (); } +/// # trait Guest {} +/// struct MyType; +/// +/// impl Guest for MyType { +/// // ... +/// } +/// +/// export!(MyType); +/// ``` +#[allow(unused_macros)] +#[doc(hidden)] +macro_rules! __export_w_impl { + ($ty:ident) => { + self::export!($ty with_types_in self); + }; + ($ty:ident with_types_in $($path_to_types_root:tt)*) => { + $($path_to_types_root)*:: exports::test::test::i::__export_test_test_i_cabi!($ty + with_types_in $($path_to_types_root)*:: exports::test::test::i); + }; +} +#[doc(inline)] +pub(crate) use __export_w_impl as export; +#[cfg(target_arch = "wasm32")] +#[link_section = "component-type:wit-bindgen:0.30.0:test:test:w:encoded world"] +#[doc(hidden)] +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 194] = *b"\ +\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07K\x01A\x02\x01A\x02\x01\ +B\x06\x01ps\x01@\x01\x01a\0\0\0\x04\0\x01f\x01\x01\x01p}\x01@\x01\x01a\x02\0\x02\ +\x04\0\x01g\x01\x03\x04\x01\x0btest:test/i\x05\0\x04\x01\x0btest:test/w\x04\0\x0b\ +\x07\x01\0\x01w\x03\0\0\0G\x09producers\x01\x0cprocessed-by\x02\x0dwit-component\ +\x070.216.0\x10wit-bindgen-rust\x060.30.0"; +#[inline(never)] +#[doc(hidden)] +pub fn __link_custom_section_describing_imports() { + wit_bindgen::rt::maybe_link_cabi_realloc(); +} diff --git a/crates/cpp/tests/symmetric_lists/wit/lists.wit b/crates/cpp/tests/symmetric_lists/wit/lists.wit new file mode 100644 index 000000000..05185805c --- /dev/null +++ b/crates/cpp/tests/symmetric_lists/wit/lists.wit @@ -0,0 +1,14 @@ +package test:test; + +interface i { + f: func(a: list) -> list; + g: func(a: list) -> list; +} + +world w { + export i; +} + +world x { + import i; +} diff --git a/crates/cpp/tests/symmetric_stream/Cargo.lock b/crates/cpp/tests/symmetric_stream/Cargo.lock new file mode 100644 index 000000000..6a70f3485 --- /dev/null +++ b/crates/cpp/tests/symmetric_stream/Cargo.lock @@ -0,0 +1,233 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "dummy-rt" +version = "0.1.0" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "libc" +version = "0.2.167" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" + +[[package]] +name = "main" +version = "0.1.0" +dependencies = [ + "stream", + "symmetric_executor", + "symmetric_stream", + "wit-bindgen-symmetric-rt", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "source" +version = "0.1.0" +dependencies = [ + "symmetric_executor", + "symmetric_stream", + "wit-bindgen-symmetric-rt", +] + +[[package]] +name = "stream" +version = "0.1.0" +dependencies = [ + "dummy-rt", + "futures", + "source", + "symmetric_stream", + "wit-bindgen-symmetric-rt", +] + +[[package]] +name = "symmetric_executor" +version = "0.1.0" +dependencies = [ + "dummy-rt", + "futures", + "libc", +] + +[[package]] +name = "symmetric_stream" +version = "0.1.0" +dependencies = [ + "dummy-rt", + "symmetric_executor", + "wit-bindgen-symmetric-rt", +] + +[[package]] +name = "syn" +version = "2.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "wit-bindgen-symmetric-rt" +version = "0.36.0" +dependencies = [ + "bitflags", + "dummy-rt", + "futures", +] diff --git a/crates/cpp/tests/symmetric_stream/Cargo.toml b/crates/cpp/tests/symmetric_stream/Cargo.toml new file mode 100644 index 000000000..dbc97472a --- /dev/null +++ b/crates/cpp/tests/symmetric_stream/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +members = [ "source", "main", "stream"] +resolver = "2" diff --git a/crates/cpp/tests/symmetric_stream/README.md b/crates/cpp/tests/symmetric_stream/README.md new file mode 100644 index 000000000..4382d75c0 --- /dev/null +++ b/crates/cpp/tests/symmetric_stream/README.md @@ -0,0 +1,70 @@ +# Native stream design + +A stream has the following members: + + - read_ready_event_send: the sending side of the read ready (data written, ready_size contains the number of elements) + - write_ready_event_send: the sending side of the write ready (addr and size are set) + - read_addr: the address of the registered buffer (valid on write_ready) + - read_size: maximum number of elements in the buffer (valid on write_ready) + - ready_size: valid number of elements in the buffer (valid on read_ready) + - active_instances: Number of references to the stream object, decreased by + +## Special values of ready + + - CLOSED: MIN (0x8000…) also EOF + - BLOCKED: -1 (normal) + - CANCELLED: 0 (TBD) + +## Sequence + +"take" means swap with idle value (read_addr=0, read_size=0, ready=-1) + +### Read + + - if ready_size is CLOSED: End of file, ready_size should be BLOCKED + - if read_addr is set wait for read_ready event + - write addr and size + - activate write_ready + - wait for read_ready + - take ready_size and process data + +### Write + + - (only initally) on EOF set ready_size to CLOSED + - wait for write_ready + - on EOF set ready_size to CLOSED + - assert addr and size is valid, ready is MIN (blocking) + - addr zero likely EOF (reader closed) + - take addr and size, write data to buffer + - store number of valid elements in ready_size + - activate read_ready + +## Functions + +A vtable is no longer necessary, but some functions enable shared implementations (perhaps interface by WIT?) + + - create_stream: Create a new stream object + - start_reading: Register buffer and send event + - start_writing: Take and return the buffer + - finish_writing: (can also set eof independently of start_write) set available + amount and trigger event + - read_amount: Take and return the number of valid elements + - read/write_ready_event: Sending side of the event + - is_ready_to_write: Whether a write needs to wait for write_ready + - is_write_closed: Whether the write side closed + - close_read/write: Close one side of the stream + +### Open questions + + - how to cancel a read? + - simply replace addr and size with zero? + If already written nothing to do. How to handle race conditions when + destroying the buffer after cancel? Perhaps a roundtrip to close + would be helpful. (activate write_ready, wait read_ready, check for EOF) + - add a write_busy flag? If addr is zero and ready BLOCKED, then wait for + ready + - how to cancel a write? + - simply flag EOF and activate read_ready + - Is a future the same data structure? + - read_size would always be one, ready_size up to one, + finish_writing and read_amount could directly close the side diff --git a/crates/cpp/tests/symmetric_stream/generate.sh b/crates/cpp/tests/symmetric_stream/generate.sh new file mode 100755 index 000000000..80176e26b --- /dev/null +++ b/crates/cpp/tests/symmetric_stream/generate.sh @@ -0,0 +1,4 @@ +#!/bin/sh +(cd stream/src ; ../../../../../../target/debug/wit-bindgen rust ../../wit/async_stream.wit --async none --symmetric) +(cd stream_cpp; ../../../../../target/debug/wit-bindgen cpp ../wit/async_stream.wit --symmetric) +cargo fmt diff --git a/crates/cpp/tests/symmetric_stream/main/Cargo.toml b/crates/cpp/tests/symmetric_stream/main/Cargo.toml new file mode 100644 index 000000000..0bdf33fa3 --- /dev/null +++ b/crates/cpp/tests/symmetric_stream/main/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "main" +version = "0.1.0" +edition = "2021" + +[dependencies] +stream = { path = "../stream" } +symmetric_executor = { path = "../../../../symmetric_executor", features = ["trace"]} +symmetric_stream = { path = "../../../../symmetric_executor/symmetric_stream" } +wit-bindgen-symmetric-rt = { path = "../../../../symmetric_executor/rust-client" } diff --git a/crates/cpp/tests/symmetric_stream/main/build.rs b/crates/cpp/tests/symmetric_stream/main/build.rs new file mode 100644 index 000000000..3096c1795 --- /dev/null +++ b/crates/cpp/tests/symmetric_stream/main/build.rs @@ -0,0 +1,9 @@ +use std::env; + +fn main() { + let out = env::var_os("OUT_DIR").unwrap(); + println!( + r"cargo:rustc-link-search={}/../../../deps", + out.into_string().unwrap() + ); +} diff --git a/crates/cpp/tests/symmetric_stream/main/src/main.rs b/crates/cpp/tests/symmetric_stream/main/src/main.rs new file mode 100644 index 000000000..e9bffe53d --- /dev/null +++ b/crates/cpp/tests/symmetric_stream/main/src/main.rs @@ -0,0 +1,60 @@ +use wit_bindgen_symmetric_rt::{ + async_support::Stream, + symmetric_stream::{Address, Buffer}, + CallbackState, +}; + +#[link(name = "stream")] +extern "C" { + pub fn testX3AtestX2Fstream_testX00create() -> usize; +} + +const DATALEN: usize = 2; + +struct CallbackInfo { + stream: Stream, + data: [u32; DATALEN], +} + +extern "C" fn ready(arg: *mut ()) -> CallbackState { + let info = unsafe { &*arg.cast::() }; + let buffer = info.stream.read_result(); + if let Some(buffer) = buffer { + let len = buffer.get_size(); + for i in 0..len as usize { + println!("data {}", info.data[i]); + } + info.stream.start_reading(buffer); + // call again + CallbackState::Pending + } else { + // finished + println!("EOF on input"); + CallbackState::Ready + } +} + +fn main() { + // let mut result_stream: *mut () = core::ptr::null_mut(); + let result_stream = unsafe { testX3AtestX2Fstream_testX00create() }; + // function should have completed (not async) + // assert!(continuation.is_null()); + let stream = unsafe { Stream::from_handle(result_stream) }; + let mut info = Box::pin(CallbackInfo { + stream: stream.clone(), + data: [0, 0], + }); + let buffer = Buffer::new( + unsafe { Address::from_handle(info.data.as_mut_ptr() as usize) }, + DATALEN as u64, + ); + stream.start_reading(buffer); + let subscription = stream.read_ready_subscribe(); + println!("Register read in main"); + wit_bindgen_symmetric_rt::register( + subscription, + ready, + (&*info as *const CallbackInfo).cast_mut().cast(), + ); + wit_bindgen_symmetric_rt::run(); +} diff --git a/crates/cpp/tests/symmetric_stream/source/Cargo.toml b/crates/cpp/tests/symmetric_stream/source/Cargo.toml new file mode 100644 index 000000000..995658bad --- /dev/null +++ b/crates/cpp/tests/symmetric_stream/source/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "source" +version = "0.1.0" +edition = "2021" + +[dependencies] +symmetric_executor = { path = "../../../../symmetric_executor" } +symmetric_stream = { path = "../../../../symmetric_executor/symmetric_stream" } +wit-bindgen-symmetric-rt = { path = "../../../../symmetric_executor/rust-client" } + +[lib] +crate-type = ["cdylib"] diff --git a/crates/cpp/tests/symmetric_stream/source/build.rs b/crates/cpp/tests/symmetric_stream/source/build.rs new file mode 100644 index 000000000..3096c1795 --- /dev/null +++ b/crates/cpp/tests/symmetric_stream/source/build.rs @@ -0,0 +1,9 @@ +use std::env; + +fn main() { + let out = env::var_os("OUT_DIR").unwrap(); + println!( + r"cargo:rustc-link-search={}/../../../deps", + out.into_string().unwrap() + ); +} diff --git a/crates/cpp/tests/symmetric_stream/source/src/lib.rs b/crates/cpp/tests/symmetric_stream/source/src/lib.rs new file mode 100644 index 000000000..b837dd7c6 --- /dev/null +++ b/crates/cpp/tests/symmetric_stream/source/src/lib.rs @@ -0,0 +1,48 @@ +use std::sync::atomic::{AtomicU32, Ordering}; + +use wit_bindgen_symmetric_rt::{async_support::Stream, register, CallbackState, EventSubscription}; + +static COUNT: AtomicU32 = AtomicU32::new(1); + +extern "C" fn timer_call(data: *mut ()) -> CallbackState { + let count = COUNT.fetch_add(1, Ordering::AcqRel); + let stream: Stream = unsafe { Stream::from_handle(data as usize) }; + if count <= 5 { + let buffer = stream.start_writing(); + let addr = buffer.get_address().take_handle() as *mut u32; + let size = buffer.capacity(); + assert!(size >= 1); + *unsafe { &mut *addr } = count; + buffer.set_size(1); + stream.finish_writing(Some(buffer)); + } + let _ = stream.take_handle(); + CallbackState::Ready +} + +extern "C" fn write_ready(data: *mut ()) -> CallbackState { + let count = COUNT.load(Ordering::Acquire); + if count > 5 { + let stream: Stream = unsafe { Stream::from_handle(data as usize) }; + // EOF + stream.finish_writing(None); + CallbackState::Ready + } else { + if count == 1 { + println!("we can write now, starting timer"); + } + let ms_30 = EventSubscription::from_timeout(30 * 1_000_000); + register(ms_30, timer_call, data); + CallbackState::Pending + } +} + +#[allow(non_snake_case)] +#[no_mangle] +pub fn testX3AtestX2Fstream_sourceX00create() -> usize { + let stream = Stream::new(); + let event = stream.write_ready_subscribe(); + let stream_copy = stream.clone(); + register(event, write_ready, stream_copy.take_handle() as *mut ()); + stream.take_handle() +} diff --git a/crates/cpp/tests/symmetric_stream/stream/Cargo.toml b/crates/cpp/tests/symmetric_stream/stream/Cargo.toml new file mode 100644 index 000000000..053228ad6 --- /dev/null +++ b/crates/cpp/tests/symmetric_stream/stream/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "stream" +version = "0.1.0" +edition = "2021" + +[dependencies] +futures = "0.3.31" +source = { path = "../source" } +#wit-bindgen = { path = "../../../../guest-rust" } +wit-bindgen-symmetric-rt = { path = "../../../../symmetric_executor/rust-client" } +symmetric_stream = { path = "../../../../symmetric_executor/symmetric_stream", features=["trace"] } + +[dependencies.wit-bindgen] +package = "dummy-rt" +path = "../../../../symmetric_executor/dummy-rt" + +[lib] +crate-type = ["cdylib"] diff --git a/crates/cpp/tests/symmetric_stream/stream/build.rs b/crates/cpp/tests/symmetric_stream/stream/build.rs new file mode 100644 index 000000000..3096c1795 --- /dev/null +++ b/crates/cpp/tests/symmetric_stream/stream/build.rs @@ -0,0 +1,9 @@ +use std::env; + +fn main() { + let out = env::var_os("OUT_DIR").unwrap(); + println!( + r"cargo:rustc-link-search={}/../../../deps", + out.into_string().unwrap() + ); +} diff --git a/crates/cpp/tests/symmetric_stream/stream/src/lib.rs b/crates/cpp/tests/symmetric_stream/stream/src/lib.rs new file mode 100644 index 000000000..9be415124 --- /dev/null +++ b/crates/cpp/tests/symmetric_stream/stream/src/lib.rs @@ -0,0 +1,26 @@ +use futures::{SinkExt, StreamExt}; +use stream_world::test::test::stream_source::create; +use wit_bindgen_symmetric_rt::async_support; + +mod stream_world; + +stream_world::export!(MyStruct with_types_in stream_world); + +struct MyStruct; + +impl stream_world::exports::test::test::stream_test::Guest for MyStruct { + fn create() -> async_support::StreamReader { + let (mut writer, reader) = stream_world::wit_stream::new(); + let mut input = create(); + + async_support::spawn(async move { + while let Some(values) = input.next().await { + println!("received {} values", values.len()); + for value in values { + writer.feed(vec![value, value + 1]).await.unwrap(); + } + } + }); + reader + } +} diff --git a/crates/cpp/tests/symmetric_stream/stream/src/stream_world.rs b/crates/cpp/tests/symmetric_stream/stream/src/stream_world.rs new file mode 100644 index 000000000..a0ef648ab --- /dev/null +++ b/crates/cpp/tests/symmetric_stream/stream/src/stream_world.rs @@ -0,0 +1,170 @@ +// Generated by `wit-bindgen` 0.40.0. DO NOT EDIT! +// Options used: +#[allow(dead_code, clippy::all)] +pub mod test { + pub mod test { + + #[allow(dead_code, unused_imports, clippy::all)] + pub mod stream_source { + #[used] + #[doc(hidden)] + static __FORCE_SECTION_REF: fn() = + super::super::super::__link_custom_section_describing_imports; + + use super::super::super::_rt; + #[allow(unused_unsafe, clippy::all)] + pub fn create() -> wit_bindgen_symmetric_rt::async_support::StreamReader { + unsafe { + #[link(wasm_import_module = "test:test/stream-source")] + #[link(name = "source")] + extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "create")] + fn testX3AtestX2Fstream_sourceX00create() -> *mut u8; + } + let ret = testX3AtestX2Fstream_sourceX00create(); + wit_bindgen_symmetric_rt::async_support::StreamReader::from_handle( + ret, + ::VTABLE, + ) + } + } + } + } +} +#[allow(dead_code, clippy::all)] +pub mod exports { + pub mod test { + pub mod test { + + #[allow(dead_code, unused_imports, clippy::all)] + pub mod stream_test { + #[used] + #[doc(hidden)] + static __FORCE_SECTION_REF: fn() = + super::super::super::super::__link_custom_section_describing_imports; + + use super::super::super::super::_rt; + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_create_cabi() -> *mut u8 { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let result0 = T::create(); + (result0).take_handle() as *mut u8 + } + pub trait Guest { + fn create() -> wit_bindgen_symmetric_rt::async_support::StreamReader; + } + #[doc(hidden)] + + macro_rules! __export_test_test_stream_test_cabi{ + ($ty:ident with_types_in $($path_to_types:tt)*) => (const _: () = { + + #[cfg_attr(target_arch = "wasm32", export_name = "create")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn testX3AtestX2Fstream_testX00create() -> *mut u8 { + $($path_to_types)*::_export_create_cabi::<$ty>() + } + };); + } + #[doc(hidden)] + pub(crate) use __export_test_test_stream_test_cabi; + } + } + } +} +mod _rt { + #![allow(dead_code, clippy::all)] + pub use alloc_crate::alloc; + pub use alloc_crate::boxed::Box; + + #[cfg(target_arch = "wasm32")] + pub fn run_ctors_once() { + wit_bindgen::rt::run_ctors_once(); + } + extern crate alloc as alloc_crate; +} +pub mod wit_stream { + #![allow(dead_code, unused_variables, clippy::all)] + + pub trait StreamPayload: Unpin + Sized + 'static { + const VTABLE: &'static wit_bindgen_symmetric_rt::async_support::StreamVtable; + } + #[doc(hidden)] + #[allow(unused_unsafe)] + pub mod vtable0 { + + unsafe fn lift(ptr: *mut u8) -> u32 { + unsafe { *ptr.cast::() } + } + unsafe fn lower(value: u32, ptr: *mut u8) { + unsafe { + *ptr.cast::() = value; + } + } + + pub static VTABLE: wit_bindgen_symmetric_rt::async_support::StreamVtable = + wit_bindgen_symmetric_rt::async_support::StreamVtable:: { + layout: unsafe { ::std::alloc::Layout::from_size_align_unchecked(4, 4) }, + lift: Some(lift), + lower: Some(lower), + }; + impl super::StreamPayload for u32 { + const VTABLE: &'static wit_bindgen_symmetric_rt::async_support::StreamVtable = + &VTABLE; + } + } + /// Creates a new Component Model `stream` with the specified payload type. + pub fn new() -> ( + wit_bindgen_symmetric_rt::async_support::StreamWriter, + wit_bindgen_symmetric_rt::async_support::StreamReader, + ) { + wit_bindgen_symmetric_rt::async_support::stream_support::new_stream(T::VTABLE) + } +} + +/// Generates `#[unsafe(no_mangle)]` functions to export the specified type as +/// the root implementation of all generated traits. +/// +/// For more information see the documentation of `wit_bindgen::generate!`. +/// +/// ```rust +/// # macro_rules! export{ ($($t:tt)*) => (); } +/// # trait Guest {} +/// struct MyType; +/// +/// impl Guest for MyType { +/// // ... +/// } +/// +/// export!(MyType); +/// ``` +#[allow(unused_macros)] +#[doc(hidden)] + +macro_rules! __export_stream_world_impl { + ($ty:ident) => (self::export!($ty with_types_in self);); + ($ty:ident with_types_in $($path_to_types_root:tt)*) => ( + $($path_to_types_root)*::exports::test::test::stream_test::__export_test_test_stream_test_cabi!($ty with_types_in $($path_to_types_root)*::exports::test::test::stream_test); + ) +} +#[doc(inline)] +pub(crate) use __export_stream_world_impl as export; + +#[cfg(target_arch = "wasm32")] +#[unsafe(link_section = "component-type:wit-bindgen:0.40.0:test:test:stream-world:encoded world")] +#[doc(hidden)] +#[allow(clippy::octal_escapes)] +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 264] = *b"\ +\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\x85\x01\x01A\x02\x01\ +A\x04\x01B\x03\x01f\x01y\x01@\0\0\0\x04\0\x06create\x01\x01\x03\0\x17test:test/s\ +tream-source\x05\0\x01B\x03\x01f\x01y\x01@\0\0\0\x04\0\x06create\x01\x01\x04\0\x15\ +test:test/stream-test\x05\x01\x04\0\x16test:test/stream-world\x04\0\x0b\x12\x01\0\ +\x0cstream-world\x03\0\0\0G\x09producers\x01\x0cprocessed-by\x02\x0dwit-componen\ +t\x070.227.1\x10wit-bindgen-rust\x060.40.0"; + +#[inline(never)] +#[doc(hidden)] +pub fn __link_custom_section_describing_imports() { + wit_bindgen::rt::maybe_link_cabi_realloc(); +} diff --git a/crates/cpp/tests/symmetric_stream/stream_cpp/Makefile b/crates/cpp/tests/symmetric_stream/stream_cpp/Makefile new file mode 100644 index 000000000..6d0513ddd --- /dev/null +++ b/crates/cpp/tests/symmetric_stream/stream_cpp/Makefile @@ -0,0 +1,13 @@ +CXXFLAGS=-g -I. -fPIC -I../../../../symmetric_executor/cpp-client -I../../../helper-types +LDFLAGS=-L../../../../symmetric_executor/cpp-client \ + -L../../../../symmetric_executor/target/debug \ + -L../target/debug/deps + +libstream.so: stream_world.o impl.o + $(CXX) -shared -o $@ $^ $(LDFLAGS) -lruntime -lsource -lsymmetric_executor -lsymmetric_stream + +clean: + -rm libstream.so stream_world.o impl.o + +run: libstream.so + LD_LIBRARY_PATH=.:../target/debug/deps ../target/debug/main diff --git a/crates/cpp/tests/symmetric_stream/stream_cpp/impl.cpp b/crates/cpp/tests/symmetric_stream/stream_cpp/impl.cpp new file mode 100644 index 000000000..a8ae91a98 --- /dev/null +++ b/crates/cpp/tests/symmetric_stream/stream_cpp/impl.cpp @@ -0,0 +1,27 @@ +#include "stream_world_cpp.h" +#include "async_support.h" +#include +#include +#include + +wit::stream exports::test::test::stream_test::Create() { + auto streampair = create_wasi_stream(); + stream_writer* streampointer = std::make_unique>(std::move(std::move(streampair).first)).release(); + wit::stream input = ::test::test::stream_source::Create(); + input.buffering(2); + std::move(input).set_reader([streampointer](wit::span data){ + if (!data.empty()) { + std::vector feed; + feed.reserve(data.size()*2); + for (auto i: data) { + feed.push_back(i); + feed.push_back(i+1); + } + streampointer->write(std::move(feed)); + } else { + // free the stream at EOF + auto release = std::unique_ptr>(streampointer); + } + }); + return std::move(streampair).second; +} diff --git a/crates/cpp/tests/symmetric_stream/stream_cpp/stream_world.cpp b/crates/cpp/tests/symmetric_stream/stream_cpp/stream_world.cpp new file mode 100644 index 000000000..5a4a5f8c6 --- /dev/null +++ b/crates/cpp/tests/symmetric_stream/stream_cpp/stream_world.cpp @@ -0,0 +1,60 @@ +// Generated by `wit-bindgen` 0.3.0. DO NOT EDIT! + +// Ensure that the *_component_type.o object is linked in +#ifdef __wasm32__ +extern void __component_type_object_force_link_stream_world(void); +void __component_type_object_force_link_stream_world_public_use_in_this_compilation_unit(void) { + __component_type_object_force_link_stream_world(); +} +#endif +#include "stream_world_cpp.h" +#include "async_support.h" +#include // realloc + +extern "C" void *cabi_realloc(void *ptr, size_t old_size, size_t align, size_t new_size); + +__attribute__((__weak__, __export_name__("cabi_realloc"))) +void *cabi_realloc(void *ptr, size_t old_size, size_t align, size_t new_size) { + (void) old_size; + if (new_size == 0) return (void*) align; + void *ret = realloc(ptr, new_size); + if (!ret) abort(); + return ret; +} + +template <> +const uint32_t wit::StreamProperties::lowered_size = 4; +template <> +uint32_t wit::StreamProperties::lift(uint8_t const*ptr) { + return *(uint32_t const*)ptr; +} +template <> +void wit::StreamProperties::lower(uint32_t && value, uint8_t *ptr) { + *(uint32_t*)ptr = value; +} + +// struct IntLifting { +// static constexpr size_t SIZE = sizeof(T); +// static T lift(uint8_t const*ptr) { +// return *(T const*)ptr; +// } +// static void lower(T&& obj, uint8_t *ptr) { +// // new ((T*)ptr) T(std::move(obj)); +// *(T*)ptr = obj; +// } +// }; + +extern "C" uint8_t* testX3AtestX2Fstream_sourceX00create(); +wit::stream test::test::stream_source::Create() +{ + auto ret = testX3AtestX2Fstream_sourceX00create(); + return lift_stream(ret); +} +extern "C" +uint8_t* testX3AtestX2Fstream_testX00create() +{ + auto result0 = exports::test::test::stream_test::Create(); + return lower_stream(std::move(result0)); +} + +// Component Adapters diff --git a/crates/cpp/tests/symmetric_stream/stream_cpp/stream_world_cpp.h b/crates/cpp/tests/symmetric_stream/stream_cpp/stream_world_cpp.h new file mode 100644 index 000000000..b77926d09 --- /dev/null +++ b/crates/cpp/tests/symmetric_stream/stream_cpp/stream_world_cpp.h @@ -0,0 +1,14 @@ +// Generated by `wit-bindgen` 0.3.0. DO NOT EDIT! +#ifndef __CPP_GUEST_BINDINGS_STREAM_WORLD_H +#define __CPP_GUEST_BINDINGS_STREAM_WORLD_H +#define WIT_SYMMETRIC +#include +#include +#include +namespace test {namespace test {namespace stream_source {wit::stream Create(); +// export_interface Interface(Id { idx: 1 }) +}}} +namespace exports {namespace test {namespace test {namespace stream_test {wit::stream Create(); +}}}} + +#endif diff --git a/crates/cpp/tests/symmetric_stream/wit/async_stream.wit b/crates/cpp/tests/symmetric_stream/wit/async_stream.wit new file mode 100644 index 000000000..f3d71d4d0 --- /dev/null +++ b/crates/cpp/tests/symmetric_stream/wit/async_stream.wit @@ -0,0 +1,14 @@ +package test:test; + +interface stream-source { + create: func() -> stream; +} + +interface stream-test { + create: func() -> stream; +} + +world stream-world { + import stream-source; + export stream-test; +} diff --git a/crates/cpp/tests/symmetric_stream_string/Cargo.lock b/crates/cpp/tests/symmetric_stream_string/Cargo.lock new file mode 100644 index 000000000..2e90e7d9d --- /dev/null +++ b/crates/cpp/tests/symmetric_stream_string/Cargo.lock @@ -0,0 +1,224 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "dummy-rt" +version = "0.1.0" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mini-bindgen" +version = "0.1.0" +dependencies = [ + "dummy-rt", + "wit-bindgen-symmetric-rt", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "symmetric_executor" +version = "0.1.0" +dependencies = [ + "dummy-rt", + "futures", + "libc", +] + +[[package]] +name = "symmetric_stream" +version = "0.1.0" +dependencies = [ + "dummy-rt", + "symmetric_executor", + "wit-bindgen-symmetric-rt", +] + +[[package]] +name = "symmetric_stream_string" +version = "0.1.0" +dependencies = [ + "futures-util", + "mini-bindgen", + "symmetric_executor", + "symmetric_stream", + "test", + "wit-bindgen-symmetric-rt", +] + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "test" +version = "0.1.0" +dependencies = [ + "mini-bindgen", + "wit-bindgen-symmetric-rt", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "wit-bindgen-symmetric-rt" +version = "0.36.0" +dependencies = [ + "dummy-rt", + "futures", +] diff --git a/crates/cpp/tests/symmetric_stream_string/Cargo.toml b/crates/cpp/tests/symmetric_stream_string/Cargo.toml new file mode 100644 index 000000000..7f105f3f2 --- /dev/null +++ b/crates/cpp/tests/symmetric_stream_string/Cargo.toml @@ -0,0 +1,15 @@ +[workspace] +members = ["test"] + +[package] +name = "symmetric_stream_string" +version = "0.1.0" +edition = "2024" + +[dependencies] +test = { path = "test" } +symmetric_executor = { path = "../../../symmetric_executor", features = ["trace"]} +symmetric_stream = { path = "../../../symmetric_executor/symmetric_stream", features = ["trace"] } +wit-bindgen-symmetric-rt = { path = "../../../symmetric_executor/rust-client" } +wit-bindgen = { path = "../../../symmetric_executor/dummy-bindgen", package = "mini-bindgen" } +futures-util = "0.3.31" diff --git a/crates/cpp/tests/symmetric_stream_string/build.rs b/crates/cpp/tests/symmetric_stream_string/build.rs new file mode 100644 index 000000000..870a9b45f --- /dev/null +++ b/crates/cpp/tests/symmetric_stream_string/build.rs @@ -0,0 +1,14 @@ +use std::env; + +fn main() { + let out = env::var_os("OUT_DIR").unwrap(); + println!( + r"cargo:rustc-link-search={}/../../../deps", + out.into_string().unwrap() + ); + let manifest = env::var_os("CARGO_MANIFEST_DIR").unwrap(); + println!( + r"cargo:rustc-env=BINDINGS={}/src/runner.rs", + manifest.into_string().unwrap() + ); +} diff --git a/crates/cpp/tests/symmetric_stream_string/format.sh b/crates/cpp/tests/symmetric_stream_string/format.sh new file mode 100755 index 000000000..4ca03aab6 --- /dev/null +++ b/crates/cpp/tests/symmetric_stream_string/format.sh @@ -0,0 +1,3 @@ +#!/bin/sh +rustfmt --edition=2024 test/src/*.rs +rustfmt --edition=2024 src/*.rs diff --git a/crates/cpp/tests/symmetric_stream_string/generate.sh b/crates/cpp/tests/symmetric_stream_string/generate.sh new file mode 100755 index 000000000..879f4d5f8 --- /dev/null +++ b/crates/cpp/tests/symmetric_stream_string/generate.sh @@ -0,0 +1,7 @@ +#!/bin/sh +cd src +export TOPLEVEL=../../../../.. +${TOPLEVEL}/target/debug/wit-bindgen rust ${TOPLEVEL}/tests/runtime-async/async/stream-string/test.wit --symmetric -w runner +cd ../test/src +export TOPLEVEL=../../../../../.. +${TOPLEVEL}/target/debug/wit-bindgen rust ${TOPLEVEL}/tests/runtime-async/async/stream-string/test.wit --symmetric -w test diff --git a/crates/cpp/tests/symmetric_stream_string/src/main.rs b/crates/cpp/tests/symmetric_stream_string/src/main.rs new file mode 120000 index 000000000..de4297e7e --- /dev/null +++ b/crates/cpp/tests/symmetric_stream_string/src/main.rs @@ -0,0 +1 @@ +../../../../../tests/runtime-async/async/stream-string/runner.rs \ No newline at end of file diff --git a/crates/cpp/tests/symmetric_stream_string/src/runner.rs b/crates/cpp/tests/symmetric_stream_string/src/runner.rs new file mode 100644 index 000000000..6b8dc9e50 --- /dev/null +++ b/crates/cpp/tests/symmetric_stream_string/src/runner.rs @@ -0,0 +1,131 @@ +// Generated by `wit-bindgen` 0.41.0. DO NOT EDIT! +// Options used: +#[allow(dead_code, clippy::all)] +pub mod a { + pub mod b { + + #[allow(dead_code, async_fn_in_trait, unused_imports, clippy::all)] + pub mod the_test { + #[used] + #[doc(hidden)] + static __FORCE_SECTION_REF: fn() = + super::super::super::__link_custom_section_describing_imports; + + use super::super::super::_rt; + #[allow(unused_unsafe, clippy::all)] + #[allow(async_fn_in_trait)] + pub fn f() -> wit_bindgen_symmetric_rt::async_support::StreamReader<_rt::String> { + unsafe { + #[link(wasm_import_module = "a:b/the-test")] + unsafe extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "f")] + fn aX3AbX2Fthe_testX00f() -> *mut u8; + } + let ret = aX3AbX2Fthe_testX00f(); + wit_bindgen_symmetric_rt::async_support::StreamReader::from_handle(ret, <_rt::String as super::super::super::wit_stream::StreamPayload>::VTABLE) + } + } + } + } +} +mod _rt { + #![allow(dead_code, clippy::all)] + pub use alloc_crate::string::String; + pub use alloc_crate::vec::Vec; + pub unsafe fn string_lift(bytes: Vec) -> String { + if cfg!(debug_assertions) { + String::from_utf8(bytes).unwrap() + } else { + unsafe { String::from_utf8_unchecked(bytes) } + } + } + pub unsafe fn cabi_dealloc(ptr: *mut u8, size: usize, align: usize) { + if size == 0 { + return; + } + unsafe { + let layout = alloc::Layout::from_size_align_unchecked(size, align); + alloc::dealloc(ptr, layout); + } + } + extern crate alloc as alloc_crate; + pub use alloc_crate::alloc; +} +pub mod wit_stream { + #![allow(dead_code, unused_variables, clippy::all)] + + pub trait StreamPayload: Unpin + Sized + 'static { + const VTABLE: &'static wit_bindgen::rt::async_support::StreamVtable; + } + #[doc(hidden)] + #[allow(unused_unsafe)] + pub mod vtable0 { + + unsafe fn lift(ptr: *mut u8) -> super::super::_rt::String { + unsafe { + let l0 = *ptr.add(0).cast::<*mut u8>(); + let l1 = *ptr.add(::core::mem::size_of::<*const u8>()).cast::(); + let len2 = l1; + let bytes2 = if len2 > 0 { + super::super::_rt::Vec::from_raw_parts(l0.cast(), len2, len2) + } else { + Default::default() + }; + + super::super::_rt::string_lift(bytes2) + } + } + unsafe fn lower(value: super::super::_rt::String, ptr: *mut u8) { + unsafe { + let vec0 = value; + let ptr0 = vec0.as_ptr().cast::(); + let len0 = vec0.len(); + *ptr.add(::core::mem::size_of::<*const u8>()).cast::() = len0; + *ptr.add(0).cast::<*mut u8>() = ptr0.cast_mut(); + } + } + unsafe fn dealloc_lists(ptr: *mut u8) { + unsafe { + let l0 = *ptr.add(0).cast::<*mut u8>(); + let l1 = *ptr.add(::core::mem::size_of::<*const u8>()).cast::(); + super::super::_rt::cabi_dealloc(l0, l1, 1); + } + } + + pub static VTABLE: wit_bindgen_symmetric_rt::async_support::StreamVtable< + super::super::_rt::String, + > = wit_bindgen_symmetric_rt::async_support::StreamVtable:: { + layout: unsafe { ::std::alloc::Layout::from_size_align_unchecked(8, 4) }, + lift: Some(lift), + lower: Some(lower), + }; + + impl super::StreamPayload for super::super::_rt::String { + const VTABLE: &'static wit_bindgen_symmetric_rt::async_support::StreamVtable = + &VTABLE; + } + } + /// Creates a new Component Model `stream` with the specified payload type. + pub fn new() -> ( + wit_bindgen_symmetric_rt::async_support::StreamWriter, + wit_bindgen_symmetric_rt::async_support::StreamReader, + ) { + wit_bindgen_symmetric_rt::async_support::stream_support::new_stream(T::VTABLE) + } +} + +#[cfg(target_arch = "wasm32")] +#[unsafe(link_section = "component-type:wit-bindgen:0.41.0:a:b:runner:encoded world")] +#[doc(hidden)] +#[allow(clippy::octal_escapes)] +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 180] = *b"\ +\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x078\x01A\x02\x01A\x02\x01\ +B\x03\x01f\x01s\x01@\0\0\0\x04\0\x01f\x01\x01\x03\0\x0ca:b/the-test\x05\0\x04\0\x0a\ +a:b/runner\x04\0\x0b\x0c\x01\0\x06runner\x03\0\0\0G\x09producers\x01\x0cprocesse\ +d-by\x02\x0dwit-component\x070.229.0\x10wit-bindgen-rust\x060.41.0"; + +#[inline(never)] +#[doc(hidden)] +pub fn __link_custom_section_describing_imports() { + wit_bindgen::rt::maybe_link_cabi_realloc(); +} diff --git a/crates/cpp/tests/symmetric_stream_string/test/Cargo.toml b/crates/cpp/tests/symmetric_stream_string/test/Cargo.toml new file mode 100644 index 000000000..a91624c3f --- /dev/null +++ b/crates/cpp/tests/symmetric_stream_string/test/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "test" +version = "0.1.0" +edition = "2021" + +[dependencies] +wit-bindgen-symmetric-rt = { path = "../../../../symmetric_executor/rust-client" } +wit-bindgen = { path = "../../../../symmetric_executor/dummy-bindgen", package = "mini-bindgen" } diff --git a/crates/cpp/tests/symmetric_stream_string/test/build.rs b/crates/cpp/tests/symmetric_stream_string/test/build.rs new file mode 100644 index 000000000..6946faa2a --- /dev/null +++ b/crates/cpp/tests/symmetric_stream_string/test/build.rs @@ -0,0 +1,9 @@ +use std::env; + +fn main() { + let manifest = env::var_os("CARGO_MANIFEST_DIR").unwrap(); + println!( + r"cargo:rustc-env=BINDINGS={}/src/test.rs", + manifest.into_string().unwrap() + ); +} diff --git a/crates/cpp/tests/symmetric_stream_string/test/src/lib.rs b/crates/cpp/tests/symmetric_stream_string/test/src/lib.rs new file mode 120000 index 000000000..ff0ca2a84 --- /dev/null +++ b/crates/cpp/tests/symmetric_stream_string/test/src/lib.rs @@ -0,0 +1 @@ +../../../../../../tests/runtime-async/async/stream-string/test.rs \ No newline at end of file diff --git a/crates/cpp/tests/symmetric_stream_string/test/src/test.rs b/crates/cpp/tests/symmetric_stream_string/test/src/test.rs new file mode 100644 index 000000000..f4f9adb4c --- /dev/null +++ b/crates/cpp/tests/symmetric_stream_string/test/src/test.rs @@ -0,0 +1,173 @@ +// Generated by `wit-bindgen` 0.41.0. DO NOT EDIT! +// Options used: +#[allow(dead_code, clippy::all)] +pub mod exports { + pub mod a { + pub mod b { + + #[allow(dead_code, async_fn_in_trait, unused_imports, clippy::all)] + pub mod the_test { + #[used] + #[doc(hidden)] + static __FORCE_SECTION_REF: fn() = + super::super::super::super::__link_custom_section_describing_imports; + + use super::super::super::super::_rt; + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_f_cabi() -> *mut u8 { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let result0 = { T::f() }; + (result0).take_handle() as *mut u8 + } + } + pub trait Guest { + #[allow(async_fn_in_trait)] + fn f() -> wit_bindgen_symmetric_rt::async_support::StreamReader<_rt::String>; + } + #[doc(hidden)] + + macro_rules! __export_a_b_the_test_cabi{ + ($ty:ident with_types_in $($path_to_types:tt)*) => (const _: () = { + + #[cfg_attr(target_arch = "wasm32", export_name = "f")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn aX3AbX2Fthe_testX00f() -> *mut u8 { + unsafe { $($path_to_types)*::_export_f_cabi::<$ty>() } + } + };); + } + #[doc(hidden)] + pub(crate) use __export_a_b_the_test_cabi; + } + } + } +} +mod _rt { + #![allow(dead_code, clippy::all)] + pub use alloc_crate::string::String; + pub use alloc_crate::vec::Vec; + pub unsafe fn cabi_dealloc(ptr: *mut u8, size: usize, align: usize) { + if size == 0 { + return; + } + unsafe { + let layout = alloc::Layout::from_size_align_unchecked(size, align); + alloc::dealloc(ptr, layout); + } + } + + #[cfg(target_arch = "wasm32")] + pub fn run_ctors_once() { + wit_bindgen::rt::run_ctors_once(); + } + extern crate alloc as alloc_crate; + pub use alloc_crate::alloc; +} +pub mod wit_stream { + #![allow(dead_code, unused_variables, clippy::all)] + + pub trait StreamPayload: Unpin + Sized + 'static { + const VTABLE: &'static wit_bindgen::rt::async_support::StreamVtable; + } + #[doc(hidden)] + #[allow(unused_unsafe)] + pub mod vtable0 { + + unsafe fn lift(ptr: *mut u8) -> super::super::_rt::String { + unsafe { + let l0 = *ptr.add(0).cast::<*mut u8>(); + let l1 = *ptr.add(::core::mem::size_of::<*const u8>()).cast::(); + let len2 = l1; + let string2 = String::from( + std::str::from_utf8(std::slice::from_raw_parts(l0, len2)).unwrap(), + ); + + string2 + } + } + unsafe fn lower(value: super::super::_rt::String, ptr: *mut u8) { + unsafe { + let vec0 = (value.into_bytes()).into_boxed_slice(); + let ptr0 = vec0.as_ptr().cast::(); + let len0 = vec0.len(); + ::core::mem::forget(vec0); + *ptr.add(::core::mem::size_of::<*const u8>()).cast::() = len0; + *ptr.add(0).cast::<*mut u8>() = ptr0.cast_mut(); + } + } + unsafe fn dealloc_lists(ptr: *mut u8) { + unsafe { + let l0 = *ptr.add(0).cast::<*mut u8>(); + let l1 = *ptr.add(::core::mem::size_of::<*const u8>()).cast::(); + super::super::_rt::cabi_dealloc(l0, l1, 1); + } + } + + pub static VTABLE: wit_bindgen_symmetric_rt::async_support::StreamVtable< + super::super::_rt::String, + > = wit_bindgen_symmetric_rt::async_support::StreamVtable:: { + layout: unsafe { ::std::alloc::Layout::from_size_align_unchecked(8, 4) }, + lift: Some(lift), + lower: Some(lower), + }; + + impl super::StreamPayload for super::super::_rt::String { + const VTABLE: &'static wit_bindgen_symmetric_rt::async_support::StreamVtable = + &VTABLE; + } + } + /// Creates a new Component Model `stream` with the specified payload type. + pub fn new() -> ( + wit_bindgen_symmetric_rt::async_support::StreamWriter, + wit_bindgen_symmetric_rt::async_support::StreamReader, + ) { + wit_bindgen_symmetric_rt::async_support::stream_support::new_stream(T::VTABLE) + } +} + +/// Generates `#[unsafe(no_mangle)]` functions to export the specified type as +/// the root implementation of all generated traits. +/// +/// For more information see the documentation of `wit_bindgen::generate!`. +/// +/// ```rust +/// # macro_rules! export{ ($($t:tt)*) => (); } +/// # trait Guest {} +/// struct MyType; +/// +/// impl Guest for MyType { +/// // ... +/// } +/// +/// export!(MyType); +/// ``` +#[allow(unused_macros)] +#[doc(hidden)] + +macro_rules! __export_test_impl { + ($ty:ident) => (self::export!($ty with_types_in self);); + ($ty:ident with_types_in $($path_to_types_root:tt)*) => ( + $($path_to_types_root)*::exports::a::b::the_test::__export_a_b_the_test_cabi!($ty with_types_in $($path_to_types_root)*::exports::a::b::the_test); + ) +} +#[doc(inline)] +pub(crate) use __export_test_impl as export; + +#[cfg(target_arch = "wasm32")] +#[unsafe(link_section = "component-type:wit-bindgen:0.41.0:a:b:test:encoded world")] +#[doc(hidden)] +#[allow(clippy::octal_escapes)] +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 176] = *b"\ +\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x076\x01A\x02\x01A\x02\x01\ +B\x03\x01f\x01s\x01@\0\0\0\x04\0\x01f\x01\x01\x04\0\x0ca:b/the-test\x05\0\x04\0\x08\ +a:b/test\x04\0\x0b\x0a\x01\0\x04test\x03\0\0\0G\x09producers\x01\x0cprocessed-by\ +\x02\x0dwit-component\x070.229.0\x10wit-bindgen-rust\x060.41.0"; + +#[inline(never)] +#[doc(hidden)] +pub fn __link_custom_section_describing_imports() { + wit_bindgen::rt::maybe_link_cabi_realloc(); +} diff --git a/crates/cpp/tests/symmetric_tests/flavorful.rs b/crates/cpp/tests/symmetric_tests/flavorful.rs new file mode 100644 index 000000000..68a5c993e --- /dev/null +++ b/crates/cpp/tests/symmetric_tests/flavorful.rs @@ -0,0 +1,150 @@ +wit_bindgen::generate!({ + path: "../tests/runtime/flavorful", + symmetric: true, + invert_direction: true, +}); + +export!(MyExports); + +use std::sync::atomic::AtomicBool; + +use exports::test::flavorful::test as test_imports; +use test::flavorful::test::*; + +#[derive(Default)] +pub struct MyExports; + +static ERRORED: AtomicBool = AtomicBool::new(false); + +impl exports::test::flavorful::test::Guest for MyExports { + fn f_list_in_record1(ty: test_imports::ListInRecord1) { + assert_eq!(ty.a, "list_in_record1"); + } + + fn f_list_in_record2() -> test_imports::ListInRecord2 { + test_imports::ListInRecord2 { + a: "list_in_record2".to_string(), + } + } + + fn f_list_in_record3(a: test_imports::ListInRecord3) -> test_imports::ListInRecord3 { + assert_eq!(a.a, "list_in_record3 input"); + test_imports::ListInRecord3 { + a: "list_in_record3 output".to_string(), + } + } + + fn f_list_in_record4(a: test_imports::ListInAlias) -> test_imports::ListInAlias { + assert_eq!(a.a, "input4"); + test_imports::ListInRecord4 { + a: "result4".to_string(), + } + } + + fn f_list_in_variant1(a: test_imports::ListInVariant1V1, b: test_imports::ListInVariant1V2) { + assert_eq!(a.unwrap(), "foo"); + assert_eq!(b.unwrap_err(), "bar"); + } + + fn f_list_in_variant2() -> Option { + Some("list_in_variant2".to_string()) + } + + fn f_list_in_variant3(a: test_imports::ListInVariant3) -> Option { + assert_eq!(a.unwrap(), "input3"); + Some("output3".to_string()) + } + + fn errno_result() -> Result<(), test_imports::MyErrno> { + if ERRORED.load(std::sync::atomic::Ordering::SeqCst) { + return Ok(()); + } + test_imports::MyErrno::A.to_string(); + format!("{:?}", test_imports::MyErrno::A); + fn assert_error() {} + assert_error::(); + ERRORED.store(true, std::sync::atomic::Ordering::SeqCst); + Err(test_imports::MyErrno::B) + } + + fn list_typedefs( + a: test_imports::ListTypedef, + b: test_imports::ListTypedef3, + ) -> (test_imports::ListTypedef2, test_imports::ListTypedef3) { + assert_eq!(a, "typedef1"); + assert_eq!(b.len(), 1); + assert_eq!(b[0], "typedef2"); + (b"typedef3".to_vec(), vec!["typedef4".to_string()]) + } + + fn list_of_variants( + bools: Vec, + results: Vec>, + enums: Vec, + ) -> (Vec, Vec>, Vec) { + assert_eq!(bools, [true, false]); + assert_eq!(results, [Ok(()), Err(())]); + assert_eq!( + enums, + [test_imports::MyErrno::Success, test_imports::MyErrno::A] + ); + ( + vec![false, true], + vec![Err(()), Ok(())], + vec![test_imports::MyErrno::A, test_imports::MyErrno::B], + ) + } +} + +pub fn main() { + test_imports(); + // let exports = exports.test_flavorful_test(); + + f_list_in_record1(&ListInRecord1 { + a: "list_in_record1".to_string(), + }); + assert_eq!(f_list_in_record2().a, "list_in_record2"); + + assert_eq!( + f_list_in_record3(&ListInRecord3 { + a: "list_in_record3 input".to_string() + }) + .a, + "list_in_record3 output" + ); + + assert_eq!( + f_list_in_record4(&ListInAlias { + a: "input4".to_string() + }) + .a, + "result4" + ); + + f_list_in_variant1(&Some("foo".to_string()), &Err("bar".to_string())); + assert_eq!(f_list_in_variant2(), Some("list_in_variant2".to_string())); + assert_eq!( + f_list_in_variant3(&Some("input3".to_string())), + Some("output3".to_string()) + ); + + assert!(errno_result().is_err()); + MyErrno::A.to_string(); + format!("{:?}", MyErrno::A); + fn assert_error() {} + assert_error::(); + + let (a, b) = list_typedefs(&"typedef1".to_string(), &vec!["typedef2".to_string()]); + assert_eq!(a, b"typedef3"); + assert_eq!(b.len(), 1); + assert_eq!(b[0], "typedef4"); + { + #[link(name = "flavorful")] + extern "C" { + fn test_imports(); + } + let _ = || { + unsafe { test_imports() }; + }; + } +} diff --git a/crates/cpp/tests/symmetric_tests/lists.rs b/crates/cpp/tests/symmetric_tests/lists.rs new file mode 100644 index 000000000..4e91a6ed5 --- /dev/null +++ b/crates/cpp/tests/symmetric_tests/lists.rs @@ -0,0 +1,134 @@ +wit_bindgen::generate!({ + path: "../tests/runtime/lists", + symmetric: true, + invert_direction: true, +}); + +export!(MyExports); + +pub struct MyExports; + +impl exports::test::lists::test::Guest for MyExports { + fn empty_list_param(a: Vec) { + assert!(a.is_empty()); + } + + fn empty_string_param(a: String) { + assert_eq!(a, ""); + } + + fn empty_list_result() -> Vec { + Vec::new() + } + + fn empty_string_result() -> String { + String::new() + } + + fn list_param(list: Vec) { + assert_eq!(list, [1, 2, 3, 4]); + } + + fn list_param2(ptr: String) { + assert_eq!(ptr, "foo"); + } + + fn list_param3(ptr: Vec) { + assert_eq!(ptr.len(), 3); + assert_eq!(ptr[0], "foo"); + assert_eq!(ptr[1], "bar"); + assert_eq!(ptr[2], "baz"); + } + + fn list_param4(ptr: Vec>) { + assert_eq!(ptr.len(), 2); + assert_eq!(ptr[0][0], "foo"); + assert_eq!(ptr[0][1], "bar"); + assert_eq!(ptr[1][0], "baz"); + } + + fn list_result() -> Vec { + vec![1, 2, 3, 4, 5] + } + + fn list_result2() -> String { + "hello!".to_string() + } + + fn list_result3() -> Vec { + vec!["hello,".to_string(), "world!".to_string()] + } + + fn list_roundtrip(list: Vec) -> Vec { + list.to_vec() + } + + fn string_roundtrip(s: String) -> String { + s.to_string() + } + + fn list_minmax8(u: Vec, s: Vec) -> (Vec, Vec) { + assert_eq!(u, [u8::MIN, u8::MAX]); + assert_eq!(s, [i8::MIN, i8::MAX]); + (u, s) + } + + fn list_minmax16(u: Vec, s: Vec) -> (Vec, Vec) { + assert_eq!(u, [u16::MIN, u16::MAX]); + assert_eq!(s, [i16::MIN, i16::MAX]); + (u, s) + } + + fn list_minmax32(u: Vec, s: Vec) -> (Vec, Vec) { + assert_eq!(u, [u32::MIN, u32::MAX]); + assert_eq!(s, [i32::MIN, i32::MAX]); + (u, s) + } + + fn list_minmax64(u: Vec, s: Vec) -> (Vec, Vec) { + assert_eq!(u, [u64::MIN, u64::MAX]); + assert_eq!(s, [i64::MIN, i64::MAX]); + (u, s) + } + + fn list_minmax_float(u: Vec, s: Vec) -> (Vec, Vec) { + assert_eq!(u, [f32::MIN, f32::MAX, f32::NEG_INFINITY, f32::INFINITY]); + assert_eq!(s, [f64::MIN, f64::MAX, f64::NEG_INFINITY, f64::INFINITY]); + (u, s) + } +} + +pub fn main() { + let bytes = allocated_bytes(); + test_imports(); + use test::lists::test::*; + empty_list_param(&[]); + empty_string_param(""); + assert!(empty_list_result().is_empty()); + assert_eq!(empty_string_result(), ""); + list_param(&[1, 2, 3, 4]); + list_param2("foo"); + list_param3(&["foo".to_owned(), "bar".to_owned(), "baz".to_owned()]); + list_param4(&[ + vec!["foo".to_owned(), "bar".to_owned()], + vec!["baz".to_owned()], + ]); + assert_eq!(list_result(), [1, 2, 3, 4, 5]); + assert_eq!(list_result2(), "hello!"); + assert_eq!(list_result3(), ["hello,", "world!"]); + assert_eq!(string_roundtrip("x"), "x"); + assert_eq!(string_roundtrip(""), ""); + assert_eq!(string_roundtrip("hello ⚑ world"), "hello ⚑ world"); + // Ensure that we properly called `free` everywhere in all the glue that we + // needed to. + assert_eq!(bytes, allocated_bytes()); + { + #[link(name = "lists")] + extern "C" { + fn test_imports(); + } + let _ = || { + unsafe { test_imports() }; + }; + } +} diff --git a/crates/cpp/tests/symmetric_tests/many_arguments.rs b/crates/cpp/tests/symmetric_tests/many_arguments.rs new file mode 100644 index 000000000..2f7d2ac90 --- /dev/null +++ b/crates/cpp/tests/symmetric_tests/many_arguments.rs @@ -0,0 +1,92 @@ +wit_bindgen::generate!({ + path: "../tests/runtime/many_arguments", + symmetric: true, + invert_direction: true, +}); + +export!(MyExports); + +pub struct MyExports {} + +impl exports::imports::Guest for MyExports { + fn many_arguments( + a1: u64, + a2: u64, + a3: u64, + a4: u64, + a5: u64, + a6: u64, + a7: u64, + a8: u64, + a9: u64, + a10: u64, + a11: u64, + a12: u64, + a13: u64, + a14: u64, + a15: u64, + a16: u64, + ) { + assert_eq!(a1, 1); + assert_eq!(a2, 2); + assert_eq!(a3, 3); + assert_eq!(a4, 4); + assert_eq!(a5, 5); + assert_eq!(a6, 6); + assert_eq!(a7, 7); + assert_eq!(a8, 8); + assert_eq!(a9, 9); + assert_eq!(a10, 10); + assert_eq!(a11, 11); + assert_eq!(a12, 12); + assert_eq!(a13, 13); + assert_eq!(a14, 14); + assert_eq!(a15, 15); + assert_eq!(a16, 16); + } +} + +fn main() { + many_arguments( + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + ); + { + #[link(name = "many_arguments")] + extern "C" { + fn many_arguments(a1: i64, + a2: i64, + a3: i64, + a4: i64, + a5: i64, + a6: i64, + a7: i64, + a8: i64, + a9: i64, + a10: i64, + a11: i64, + a12: i64, + a13: i64, + a14: i64, + a15: i64, + a16: i64,); + } + let _ = || { + unsafe { many_arguments(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,) }; + }; + } +} diff --git a/crates/cpp/tests/symmetric_tests/numbers.rs b/crates/cpp/tests/symmetric_tests/numbers.rs new file mode 100644 index 000000000..957af3d25 --- /dev/null +++ b/crates/cpp/tests/symmetric_tests/numbers.rs @@ -0,0 +1,131 @@ +use std::sync::atomic::AtomicU32; + +wit_bindgen::generate!({ + path: "../tests/runtime/numbers", + symmetric: true, + invert_direction: true, +}); + +static SCALAR: AtomicU32 = AtomicU32::new(0); + +export!(MyExports); + +pub struct MyExports; + +impl exports::test::numbers::test::Guest for MyExports { + fn roundtrip_u8(a: u8) -> u8 { + a + } + + fn roundtrip_s8(a: i8) -> i8 { + a + } + + fn roundtrip_u16(a: u16) -> u16 { + a + } + + fn roundtrip_s16(a: i16) -> i16 { + a + } + + fn roundtrip_u32(a: u32) -> u32 { + a + } + + fn roundtrip_s32(a: i32) -> i32 { + a + } + + fn roundtrip_u64(a: u64) -> u64 { + a + } + + fn roundtrip_s64(a: i64) -> i64 { + a + } + + fn roundtrip_f32(a: f32) -> f32 { + a + } + + fn roundtrip_f64(a: f64) -> f64 { + a + } + + fn roundtrip_char(a: char) -> char { + a + } + + fn set_scalar(a: u32) -> () { + SCALAR.store(a, std::sync::atomic::Ordering::SeqCst); + } + + fn get_scalar() -> u32 { + SCALAR.load(std::sync::atomic::Ordering::SeqCst) + } +} + +pub fn main() { + test_imports(); + use test::numbers::test::*; + assert_eq!(roundtrip_u8(1), 1); + assert_eq!(roundtrip_u8(u8::min_value()), u8::min_value()); + assert_eq!(roundtrip_u8(u8::max_value()), u8::max_value()); + + assert_eq!(roundtrip_s8(1), 1); + assert_eq!(roundtrip_s8(i8::min_value()), i8::min_value()); + assert_eq!(roundtrip_s8(i8::max_value()), i8::max_value()); + + assert_eq!(roundtrip_u16(1), 1); + assert_eq!(roundtrip_u16(u16::min_value()), u16::min_value()); + assert_eq!(roundtrip_u16(u16::max_value()), u16::max_value()); + + assert_eq!(roundtrip_s16(1), 1); + assert_eq!(roundtrip_s16(i16::min_value()), i16::min_value()); + assert_eq!(roundtrip_s16(i16::max_value()), i16::max_value()); + + assert_eq!(roundtrip_u32(1), 1); + assert_eq!(roundtrip_u32(u32::min_value()), u32::min_value()); + assert_eq!(roundtrip_u32(u32::max_value()), u32::max_value()); + + assert_eq!(roundtrip_s32(1), 1); + assert_eq!(roundtrip_s32(i32::min_value()), i32::min_value()); + assert_eq!(roundtrip_s32(i32::max_value()), i32::max_value()); + + assert_eq!(roundtrip_u64(1), 1); + assert_eq!(roundtrip_u64(u64::min_value()), u64::min_value()); + assert_eq!(roundtrip_u64(u64::max_value()), u64::max_value()); + + assert_eq!(roundtrip_s64(1), 1); + assert_eq!(roundtrip_s64(i64::min_value()), i64::min_value()); + assert_eq!(roundtrip_s64(i64::max_value()), i64::max_value()); + + assert_eq!(roundtrip_f32(1.0), 1.0); + assert_eq!(roundtrip_f32(f32::INFINITY), f32::INFINITY); + assert_eq!(roundtrip_f32(f32::NEG_INFINITY), f32::NEG_INFINITY); + assert!(roundtrip_f32(f32::NAN).is_nan()); + + assert_eq!(roundtrip_f64(1.0), 1.0); + assert_eq!(roundtrip_f64(f64::INFINITY), f64::INFINITY); + assert_eq!(roundtrip_f64(f64::NEG_INFINITY), f64::NEG_INFINITY); + assert!(roundtrip_f64(f64::NAN).is_nan()); + + assert_eq!(roundtrip_char('a'), 'a'); + assert_eq!(roundtrip_char(' '), ' '); + assert_eq!(roundtrip_char('🚩'), '🚩'); + + set_scalar(2); + assert_eq!(get_scalar(), 2); + set_scalar(4); + assert_eq!(get_scalar(), 4); + { + #[link(name = "numbers")] + extern "C" { + fn test_imports(); + } + let _ = || { + unsafe { test_imports() }; + }; + } +} diff --git a/crates/cpp/tests/symmetric_tests/options.rs b/crates/cpp/tests/symmetric_tests/options.rs new file mode 100644 index 000000000..f6d996fec --- /dev/null +++ b/crates/cpp/tests/symmetric_tests/options.rs @@ -0,0 +1,57 @@ +wit_bindgen::generate!({ + path: "../tests/runtime/options", + symmetric: true, + invert_direction: true, +}); + +export!(MyExports); + +pub struct MyExports; + +impl exports::test::options::test::Guest for MyExports { + fn option_none_param(a: Option) { + assert!(a.is_none()); + } + + fn option_none_result() -> Option { + None + } + + fn option_some_param(a: Option) { + assert_eq!(a, Some("foo".to_string())); + } + + fn option_some_result() -> Option { + Some("foo".to_string()) + } + + fn option_roundtrip(a: Option) -> Option { + a + } + + fn double_option_roundtrip(a: Option>) -> Option> { + a + } +} + +pub fn main() { + use test::options::test::*; + test_imports(); + assert!(option_none_result().is_none()); + assert_eq!(option_some_result(), Some("foo".to_string())); + option_none_param(None); + option_some_param(Some("foo")); + assert_eq!(option_roundtrip(Some("foo")), Some("foo".to_string())); + assert_eq!(double_option_roundtrip(Some(Some(42))), Some(Some(42))); + assert_eq!(double_option_roundtrip(Some(None)), Some(None)); + assert_eq!(double_option_roundtrip(None), None); + { + #[link(name = "options")] + extern "C" { + fn test_imports(); + } + let _ = || { + unsafe { test_imports() }; + }; + } +} diff --git a/crates/cpp/tests/symmetric_tests/records.rs b/crates/cpp/tests/symmetric_tests/records.rs new file mode 100644 index 000000000..13fee7a53 --- /dev/null +++ b/crates/cpp/tests/symmetric_tests/records.rs @@ -0,0 +1,94 @@ +wit_bindgen::generate!({ + path: "../tests/runtime/records", + symmetric: true, + invert_direction: true, +}); + +export!(MyExports); + +pub struct MyExports; + +use exports::test::records::test as test_imports; + +impl test_imports::Guest for MyExports { + fn multiple_results() -> (u8, u16) { + (4, 5) + } + + fn swap_tuple(a: (u8, u32)) -> (u32, u8) { + (a.1, a.0) + } + + fn roundtrip_flags1(a: test_imports::F1) -> test_imports::F1 { + drop(format!("{:?}", a)); + let _ = a & test_imports::F1::all(); + a + } + + fn roundtrip_flags2(a: test_imports::F2) -> test_imports::F2 { + a + } + + fn roundtrip_flags3( + a: test_imports::Flag8, + b: test_imports::Flag16, + c: test_imports::Flag32, + ) -> ( + test_imports::Flag8, + test_imports::Flag16, + test_imports::Flag32, + ) { + (a, b, c) + } + + fn roundtrip_record1(a: test_imports::R1) -> test_imports::R1 { + drop(format!("{:?}", a)); + a + } + + fn tuple1(a: (u8,)) -> (u8,) { + (a.0,) + } +} + +pub fn main() { + use test::records::test::*; + + test_imports(); + assert_eq!(multiple_results(), (100, 200)); + assert_eq!(swap_tuple((1u8, 2u32)), (2u32, 1u8)); + assert_eq!(roundtrip_flags1(F1::A), F1::A); + assert_eq!(roundtrip_flags1(F1::empty()), F1::empty()); + assert_eq!(roundtrip_flags1(F1::B), F1::B); + assert_eq!(roundtrip_flags1(F1::A | F1::B), F1::A | F1::B); + + assert_eq!(roundtrip_flags2(F2::C), F2::C); + assert_eq!(roundtrip_flags2(F2::empty()), F2::empty()); + assert_eq!(roundtrip_flags2(F2::D), F2::D); + assert_eq!(roundtrip_flags2(F2::C | F2::E), F2::C | F2::E); + + let r = roundtrip_record1(R1 { + a: 8, + b: F1::empty(), + }); + assert_eq!(r.a, 8); + assert_eq!(r.b, F1::empty()); + + let r = roundtrip_record1(R1 { + a: 0, + b: F1::A | F1::B, + }); + assert_eq!(r.a, 0); + assert_eq!(r.b, F1::A | F1::B); + + assert_eq!(tuple1((1,)), (1,)); + { + #[link(name = "records")] + extern "C" { + fn test_imports(); + } + let _ = || { + unsafe { test_imports() }; + }; + } +} diff --git a/crates/cpp/tests/symmetric_tests/results.rs b/crates/cpp/tests/symmetric_tests/results.rs new file mode 100644 index 000000000..3422dde38 --- /dev/null +++ b/crates/cpp/tests/symmetric_tests/results.rs @@ -0,0 +1,136 @@ +wit_bindgen::generate!({ + path: "../tests/runtime/results", + symmetric: true, + invert_direction: true, +}); + +export!(MyExports); + +pub struct MyExports; + +use exports::test::results::test as imports; + +impl exports::test::results::test::Guest for MyExports { + fn string_error(a: f32) -> Result { + if a == 0.0 { + Err("zero".to_owned()) + } else { + Ok(a) + } + } + + fn enum_error(a: f32) -> Result { + if a == 0.0 { + Err(imports::E::A) + } else { + Ok(a) + } + } + + fn record_error(a: f32) -> Result { + if a == 0.0 { + Err(imports::E2 { + line: 420, + column: 0, + }) + } else if a == 1.0 { + Err(imports::E2 { + line: 77, + column: 2, + }) + } else { + Ok(a) + } + } + + fn variant_error(a: f32) -> Result { + if a == 0.0 { + Err(imports::E3::E2(imports::E2 { + line: 420, + column: 0, + })) + } else if a == 1.0 { + Err(imports::E3::E1(imports::E::B)) + } else if a == 2.0 { + Err(imports::E3::E1(imports::E::C)) + } else { + Ok(a) + } + } + + fn empty_error(a: u32) -> Result { + if a == 0 { + Err(()) + } else if a == 1 { + Ok(42) + } else { + Ok(a) + } + } + + fn double_error(a: u32) -> Result, String> { + if a == 0 { + Ok(Ok(())) + } else if a == 1 { + Ok(Err("one".into())) + } else { + Err("two".into()) + } + } +} + +pub fn main() { + use test::results::test::{ + double_error, empty_error, enum_error, record_error, string_error, variant_error, E, E2, E3, + }; + + assert_eq!(string_error(0.0), Err("zero".to_owned())); + assert_eq!(string_error(1.0), Ok(1.0)); + + assert_eq!(enum_error(0.0), Err(E::A)); + assert_eq!(enum_error(0.0), Err(E::A)); + + assert!(matches!( + record_error(0.0), + Err(E2 { + line: 420, + column: 0 + }) + )); + assert!(matches!( + record_error(1.0), + Err(E2 { + line: 77, + column: 2 + }) + )); + + assert!(record_error(2.0).is_ok()); + + assert!(matches!( + variant_error(0.0), + Err(E3::E2(E2 { + line: 420, + column: 0 + })) + )); + assert!(matches!(variant_error(1.0), Err(E3::E1(E::B)))); + assert!(matches!(variant_error(2.0), Err(E3::E1(E::C)))); + + assert_eq!(empty_error(0), Err(())); + assert_eq!(empty_error(1), Ok(42)); + assert_eq!(empty_error(2), Ok(2)); + + assert_eq!(double_error(0), Ok(Ok(()))); + assert_eq!(double_error(1), Ok(Err("one".into()))); + assert_eq!(double_error(2), Err("two".into())); + { + #[link(name = "results")] + extern "C" { + fn exp_testX3AresultsX2FtestX00string_error(a: f32, b: *mut u8); + } + let _ = || { + unsafe { exp_testX3AresultsX2FtestX00string_error(0.0, std::ptr::null_mut()) }; + }; + } +} diff --git a/crates/cpp/tests/symmetric_tests/smoke.rs b/crates/cpp/tests/symmetric_tests/smoke.rs new file mode 100644 index 000000000..1cf3e06d2 --- /dev/null +++ b/crates/cpp/tests/symmetric_tests/smoke.rs @@ -0,0 +1,34 @@ +use std::sync::atomic::AtomicBool; + +wit_bindgen::generate!({ + path: "../tests/runtime/smoke", + symmetric: true, + invert_direction: true, +}); + +export!(MyExports); + +pub struct MyExports; + +static HIT: AtomicBool = AtomicBool::new(false); + +impl exports::test::smoke::imports::Guest for MyExports { + fn thunk() { + HIT.store(true, std::sync::atomic::Ordering::SeqCst); + println!("tester called"); + } +} + +pub fn main() { + thunk(); + assert!(HIT.load(std::sync::atomic::Ordering::SeqCst)); + { + #[link(name = "smoke")] + extern "C" { + fn thunk(); + } + let _ = || { + unsafe { thunk() }; + }; + } +} diff --git a/crates/cpp/tests/symmetric_tests/strings.rs b/crates/cpp/tests/symmetric_tests/strings.rs new file mode 100644 index 000000000..74b49861c --- /dev/null +++ b/crates/cpp/tests/symmetric_tests/strings.rs @@ -0,0 +1,38 @@ +wit_bindgen::generate!({ + path: "../tests/runtime/strings", + symmetric: true, + invert_direction: true, +}); + +export!(MyExports); + +pub struct MyExports; + +impl exports::test::strings::imports::Guest for MyExports { + fn take_basic(s: String) { + assert_eq!(s, "latin utf16"); + } + + fn return_unicode() -> String { + "🚀🚀🚀 𠈄𓀀".to_string() + } +} + +pub fn main() { + test_imports(); + assert_eq!(return_empty(), ""); + assert_eq!(roundtrip("str"), "str"); + assert_eq!( + roundtrip("🚀🚀🚀 𠈄𓀀"), + "🚀🚀🚀 𠈄𓀀" + ); + { + #[link(name = "strings")] + extern "C" { + fn roundtrip(_: *mut u8, _: usize, _: *mut u8); + } + let _ = || { + unsafe { roundtrip(core::ptr::null_mut(), 0, core::ptr::null_mut()) }; + }; + } +} diff --git a/crates/cpp/tests/symmetric_tests/test-rust-wasm/Cargo.toml b/crates/cpp/tests/symmetric_tests/test-rust-wasm/Cargo.toml new file mode 100644 index 000000000..4b0349221 --- /dev/null +++ b/crates/cpp/tests/symmetric_tests/test-rust-wasm/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "test-rust-wasm" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/crates/cpp/tests/symmetric_tests/test-rust-wasm/src/lib.rs b/crates/cpp/tests/symmetric_tests/test-rust-wasm/src/lib.rs new file mode 100644 index 000000000..81f77ce4a --- /dev/null +++ b/crates/cpp/tests/symmetric_tests/test-rust-wasm/src/lib.rs @@ -0,0 +1,15 @@ +// checking balanced memory can't work in symmetric because ownership is transferred both ways + +pub fn get() -> usize { + 0 +} + +pub fn guard() -> impl Drop { + struct A; + + impl Drop for A { + fn drop(&mut self) {} + } + + A +} diff --git a/crates/cpp/tests/wasm-micro-runtime b/crates/cpp/tests/wasm-micro-runtime new file mode 160000 index 000000000..028f43bc1 --- /dev/null +++ b/crates/cpp/tests/wasm-micro-runtime @@ -0,0 +1 @@ +Subproject commit 028f43bc18494866c44666e54e9c5a2cd84152f5 diff --git a/crates/guest-rust/macro/src/lib.rs b/crates/guest-rust/macro/src/lib.rs index ae4635708..a85d98fd0 100644 --- a/crates/guest-rust/macro/src/lib.rs +++ b/crates/guest-rust/macro/src/lib.rs @@ -109,6 +109,9 @@ impl Parse for Config { Opt::Stubs => { opts.stubs = true; } + Opt::Wasm64 => { + opts.wasm64 = true; + } Opt::ExportPrefix(prefix) => opts.export_prefix = Some(prefix.value()), Opt::AdditionalDerives(paths) => { opts.additional_derive_attributes = paths @@ -148,6 +151,12 @@ impl Parse for Config { Opt::DisableCustomSectionLinkHelpers(disable) => { opts.disable_custom_section_link_helpers = disable.value(); } + Opt::Symmetric(enable) => { + opts.symmetric = enable.value(); + } + Opt::InvertDirection(enable) => { + opts.invert_direction = enable.value(); + } Opt::Debug(enable) => { debug = enable.value(); } @@ -247,7 +256,7 @@ fn parse_source( }; let (pkg, sources) = resolve.push_path(normalized_path)?; pkgs.push(pkg); - files.extend(sources.paths().map(|p| p.to_owned())); + files.extend(sources.package_paths(pkg).unwrap().map(|v| v.to_owned())); } Ok(()) }; @@ -266,9 +275,14 @@ fn parse_source( } impl Config { - fn expand(self) -> Result { + fn expand(mut self) -> Result { let mut files = Default::default(); + // for testing the symmetric ABI (modify guest code) + if std::env::var("SYMMETRIC_ABI").is_ok() { + self.opts.symmetric = true; + } let mut generator = self.opts.build(); + generator.apply_resolve_options(&mut self.resolve, &mut self.world); generator .generate(&self.resolve, self.world, &mut files) .map_err(|e| anyhow_to_syn(Span::call_site(), e))?; @@ -336,9 +350,12 @@ mod kw { syn::custom_keyword!(default_bindings_module); syn::custom_keyword!(export_macro_name); syn::custom_keyword!(pub_export_macro); + syn::custom_keyword!(wasm64); syn::custom_keyword!(generate_unused_types); syn::custom_keyword!(features); syn::custom_keyword!(disable_custom_section_link_helpers); + syn::custom_keyword!(symmetric); + syn::custom_keyword!(invert_direction); syn::custom_keyword!(imports); syn::custom_keyword!(debug); } @@ -392,9 +409,12 @@ enum Opt { DefaultBindingsModule(syn::LitStr), ExportMacroName(syn::LitStr), PubExportMacro(syn::LitBool), + Wasm64, GenerateUnusedTypes(syn::LitBool), Features(Vec), DisableCustomSectionLinkHelpers(syn::LitBool), + Symmetric(syn::LitBool), + InvertDirection(syn::LitBool), Async(AsyncFilterSet, Span), Debug(syn::LitBool), } @@ -487,6 +507,9 @@ impl Parse for Opt { } else if l.peek(kw::stubs) { input.parse::()?; Ok(Opt::Stubs) + } else if l.peek(kw::wasm64) { + input.parse::()?; + Ok(Opt::Wasm64) } else if l.peek(kw::export_prefix) { input.parse::()?; input.parse::()?; @@ -551,6 +574,14 @@ impl Parse for Opt { input.parse::()?; input.parse::()?; Ok(Opt::DisableCustomSectionLinkHelpers(input.parse()?)) + } else if l.peek(kw::symmetric) { + input.parse::()?; + input.parse::()?; + Ok(Opt::Symmetric(input.parse()?)) + } else if l.peek(kw::invert_direction) { + input.parse::()?; + input.parse::()?; + Ok(Opt::InvertDirection(input.parse()?)) } else if l.peek(kw::debug) { input.parse::()?; input.parse::()?; diff --git a/crates/guest-rust/rt/Cargo.toml b/crates/guest-rust/rt/Cargo.toml index 83294ec60..21f4a69ed 100644 --- a/crates/guest-rust/rt/Cargo.toml +++ b/crates/guest-rust/rt/Cargo.toml @@ -17,3 +17,5 @@ once_cell = { version = "1.19.0", optional = true } [features] async = ["dep:futures", "dep:once_cell"] +# only for compatibility never used inside this crate +symmetric = [] diff --git a/crates/guest-rust/rt/src/async_support/abi_buffer.rs b/crates/guest-rust/rt/src/async_support/abi_buffer.rs index 1ceb30e25..ec8b7a04b 100644 --- a/crates/guest-rust/rt/src/async_support/abi_buffer.rs +++ b/crates/guest-rust/rt/src/async_support/abi_buffer.rs @@ -125,6 +125,7 @@ impl AbiBuffer { /// This signals that `amt` items are no longer going to be yielded from /// `abi_ptr_and_len`. Additionally this will perform any deallocation /// necessary for the starting `amt` items in this list. + #[cfg(not(feature = "symmetric"))] pub(crate) fn advance(&mut self, amt: usize) { assert!(amt + self.cursor <= self.rust_storage.len()); let Some(dealloc_lists) = self.vtable.dealloc_lists else { diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index 9cfd1416d..455397ec0 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -2,8 +2,8 @@ use crate::{int_repr, to_rust_ident, Identifier, InterfaceGenerator, RustFlagsRe use heck::*; use std::fmt::Write as _; use std::mem; -use wit_bindgen_core::abi::{Bindgen, Instruction, LiftLower, WasmType}; -use wit_bindgen_core::{dealias, uwrite, uwriteln, wit_parser::*, Source}; +use wit_bindgen_core::abi::{AbiVariant, Bindgen, Instruction, LiftLower, WasmType}; +use wit_bindgen_core::{dealias, make_external_symbol, uwrite, uwriteln, wit_parser::*, Source}; pub(super) struct FunctionBindgen<'a, 'b> { pub r#gen: &'b mut InterfaceGenerator<'a>, @@ -52,9 +52,19 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { } } - fn declare_import(&mut self, name: &str, params: &[WasmType], results: &[WasmType]) -> String { - let tmp = self.tmp(); - let rust_name = format!("wit_import{tmp}"); + fn declare_import( + &mut self, + module_prefix: &str, + name: &str, + params: &[WasmType], + results: &[WasmType], + ) -> String { + let rust_name = String::from(module_prefix) + + &make_external_symbol(self.wasm_import_module, name, AbiVariant::GuestImport); + if let Some(library) = &self.r#gen.r#gen.opts.link_name { + self.src + .push_str(&format!("\n#[link(name = \"{}\")]", library)); + } self.src.push_str(&crate::declare_import( self.wasm_import_module, name, @@ -162,6 +172,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { || match self.lift_lower() { LiftLower::LowerArgsLiftResults => false, LiftLower::LiftArgsLowerResults => true, + LiftLower::Symmetric => todo!(), }; self.r#gen.type_path(id, owned) } @@ -371,7 +382,14 @@ impl Bindgen for FunctionBindgen<'_, '_> { .. } => { let op = &operands[0]; - let result = format!("({op}).take_handle() as i32"); + let result = format!( + "({op}).take_handle(){cast}", + cast = if self.r#gen.r#gen.opts.symmetric { + " as *mut u8" + } else { + " as i32" + } + ); results.push(result); } @@ -380,7 +398,14 @@ impl Bindgen for FunctionBindgen<'_, '_> { .. } => { let op = &operands[0]; - results.push(format!("({op}).handle() as i32")) + results.push(format!( + "({op}).handle(){cast}", + cast = if self.r#gen.r#gen.opts.symmetric { + " as *mut u8" + } else { + " as i32" + } + )) } Instruction::HandleLift { handle, .. } => { @@ -394,23 +419,36 @@ impl Bindgen for FunctionBindgen<'_, '_> { let result = if is_own { let name = self.r#gen.type_path(dealiased_resource, true); - format!("{name}::from_handle({op} as u32)") + + format!( + "{name}::from_handle({op}{cast})", + cast = if self.r#gen.r#gen.opts.symmetric { + " as usize" + } else { + " as u32" + } + ) } else if self.r#gen.is_exported_resource(*resource) { let name = resolve.types[*resource] .name .as_deref() .unwrap() .to_upper_camel_case(); - format!("{name}Borrow::lift({op} as u32 as usize)") + format!("{name}Borrow::lift({op} as usize)") } else { let tmp = format!("handle{}", self.tmp()); self.handle_decls.push(format!("let {tmp};")); let name = self.r#gen.type_path(dealiased_resource, true); format!( "{{\n - {tmp} = {name}::from_handle({op} as u32); + {tmp} = {name}::from_handle({op}{cast}); &{tmp} - }}" + }}", + cast = if self.r#gen.r#gen.opts.symmetric { + " as usize" + } else { + " as u32" + } ) }; results.push(result); @@ -418,7 +456,11 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::FutureLower { .. } => { let op = &operands[0]; - results.push(format!("({op}).take_handle() as i32")) + if self.r#gen.r#gen.opts.symmetric { + results.push(format!("({op}).take_handle() as *mut u8")) + } else { + results.push(format!("({op}).take_handle() as i32")) + } } Instruction::FutureLift { payload, .. } => { @@ -438,15 +480,24 @@ impl Bindgen for FunctionBindgen<'_, '_> { .get_index_of(&name) .unwrap(); let path = self.r#gen.path_to_root(); + let recast = if self.r#gen.r#gen.opts.symmetric { + "" + } else { + " as u32" + }; results.push(format!( "{async_support}::FutureReader::new\ - ({op} as u32, &{path}wit_future::vtable{ordinal}::VTABLE)" - )) + ({op}{recast}, &{path}wit_future::vtable{ordinal}::VTABLE)" + )); } Instruction::StreamLower { .. } => { let op = &operands[0]; - results.push(format!("({op}).take_handle() as i32")) + if self.r#gen.r#gen.opts.symmetric { + results.push(format!("({op}).take_handle() as *mut u8")) + } else { + results.push(format!("({op}).take_handle() as i32")) + } } Instruction::StreamLift { payload, .. } => { @@ -466,9 +517,14 @@ impl Bindgen for FunctionBindgen<'_, '_> { .get_index_of(&name) .unwrap(); let path = self.r#gen.path_to_root(); + let recast = if self.r#gen.r#gen.opts.symmetric { + "" + } else { + " as u32" + }; results.push(format!( "{async_support}::StreamReader::new\ - ({op} as u32, &{path}wit_stream::vtable{ordinal}::VTABLE)" + ({op}{recast}, &{path}wit_stream::vtable{ordinal}::VTABLE)" )) } @@ -667,7 +723,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { let val = format!("vec{}", tmp); let ptr = format!("ptr{}", tmp); let len = format!("len{}", tmp); - if realloc.is_none() { + if realloc.is_none() || (self.r#gen.in_import && self.r#gen.r#gen.opts.symmetric) { self.push_str(&format!("let {} = {};\n", val, operands[0])); } else { let op0 = operands.pop().unwrap(); @@ -675,7 +731,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } self.push_str(&format!("let {} = {}.as_ptr().cast::();\n", ptr, val)); self.push_str(&format!("let {} = {}.len();\n", len, val)); - if realloc.is_some() { + if realloc.is_some() && !(self.r#gen.in_import && self.r#gen.r#gen.opts.symmetric) { self.push_str(&format!("::core::mem::forget({});\n", val)); } results.push(format!("{ptr}.cast_mut()")); @@ -687,10 +743,17 @@ impl Bindgen for FunctionBindgen<'_, '_> { let len = format!("len{}", tmp); self.push_str(&format!("let {} = {};\n", len, operands[1])); let vec = self.r#gen.path_to_vec(); - let result = format!( - "{vec}::from_raw_parts({}.cast(), {1}, {1})", - operands[0], len - ); + let result = if !self.r#gen.r#gen.opts.symmetric || self.r#gen.in_import { + format!( + "{vec}::from_raw_parts({}.cast(), {1}, {1})", + operands[0], len + ) + } else { + format!( + "unsafe {{ std::slice::from_raw_parts({}.cast(), {1}) }}.to_vec()", + operands[0], len + ) + }; results.push(result); } @@ -699,7 +762,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { let val = format!("vec{}", tmp); let ptr = format!("ptr{}", tmp); let len = format!("len{}", tmp); - if realloc.is_none() { + if realloc.is_none() || (self.r#gen.in_import && self.r#gen.r#gen.opts.symmetric) { self.push_str(&format!("let {} = {};\n", val, operands[0])); } else { let op0 = format!("{}.into_bytes()", operands[0]); @@ -707,7 +770,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } self.push_str(&format!("let {} = {}.as_ptr().cast::();\n", ptr, val)); self.push_str(&format!("let {} = {}.len();\n", len, val)); - if realloc.is_some() { + if realloc.is_some() && !(self.r#gen.in_import && self.r#gen.r#gen.opts.symmetric) { self.push_str(&format!("::core::mem::forget({});\n", val)); } results.push(format!("{ptr}.cast_mut()")); @@ -715,19 +778,39 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::StringLift => { - let vec = self.r#gen.path_to_vec(); let tmp = self.tmp(); let len = format!("len{}", tmp); uwriteln!(self.src, "let {len} = {};", operands[1]); - uwriteln!( - self.src, - "let bytes{tmp} = {vec}::from_raw_parts({}.cast(), {len}, {len});", - operands[0], - ); - if self.r#gen.r#gen.opts.raw_strings { - results.push(format!("bytes{tmp}")); + if self.r#gen.r#gen.opts.symmetric && !self.r#gen.in_import { + uwriteln!( + self.src, + "let string{tmp} = String::from(std::str::from_utf8(std::slice::from_raw_parts({}, {len})).unwrap());", + operands[0], + ); + results.push(format!("string{tmp}")); } else { - results.push(format!("{}(bytes{tmp})", self.r#gen.path_to_string_lift())); + let vec = self.r#gen.path_to_vec(); + if self.r#gen.r#gen.opts.symmetric { + // symmetric must not access zero page memory + uwriteln!( + self.src, + "let bytes{tmp} = if {len}>0 {{ + {vec}::from_raw_parts({}.cast(), {len}, {len}) + }} else {{ Default::default() }};", + operands[0] + ); + } else { + uwriteln!( + self.src, + "let bytes{tmp} = {vec}::from_raw_parts({}.cast(), {len}, {len});", + operands[0], + ); + } + if self.r#gen.r#gen.opts.raw_strings { + results.push(format!("bytes{tmp}")); + } else { + results.push(format!("{}(bytes{tmp})", self.r#gen.path_to_string_lift())); + } } } @@ -755,6 +838,20 @@ impl Bindgen for FunctionBindgen<'_, '_> { self.push_str(&format!( "let ({result}, {cleanup}) = {rt}::Cleanup::new({layout});" )); + if self.r#gen.r#gen.opts.symmetric && self.r#gen.in_import { + //if !self.r#gen.needs_deallocate { + // self.push_str("// "); + //} else { + assert!(self.r#gen.needs_deallocate); + //} + self.push_str(&format!( + "if !{result}.is_null() {{ _deallocate.push(({result}, {layout})); }}\n" + )); + } + // self.push_str(&format!( + // "if ptr.is_null()\n{{\n{alloc}::handle_alloc_error({layout});\n}}\nptr\n}}", + // )); + // self.push_str("else {\n::core::ptr::null_mut()\n};\n"); if realloc.is_none() { // If an allocator isn't requested then we must clean up the // allocation ourselves since our callee isn't taking @@ -808,20 +905,34 @@ impl Bindgen for FunctionBindgen<'_, '_> { uwriteln!(self.src, "{result}.push(e{tmp});"); uwriteln!(self.src, "}}"); results.push(result); - let dealloc = self.r#gen.path_to_cabi_dealloc(); - self.push_str(&format!( - "{dealloc}({base}, {len} * {size}, {align});\n", - size = size.format(POINTER_SIZE_EXPRESSION), - align = align.format(POINTER_SIZE_EXPRESSION) - )); + if !self.r#gen.r#gen.opts.symmetric || self.r#gen.in_import { + let dealloc = self.r#gen.path_to_cabi_dealloc(); + self.push_str(&format!( + "{dealloc}({base}, {len} * {size}, {align});\n", + size = size.format(POINTER_SIZE_EXPRESSION), + align = align.format(POINTER_SIZE_EXPRESSION) + )); + } } Instruction::IterElem { .. } => results.push("e".to_string()), Instruction::IterBasePointer => results.push("base".to_string()), - Instruction::CallWasm { name, sig, .. } => { - let func = self.declare_import(name, &sig.params, &sig.results); + Instruction::CallWasm { + name, + sig, + module_prefix, + .. + } => { + let module_prefix = if let Some(prefix) = self.r#gen.r#gen.import_prefix.as_ref() { + let combined_prefix = prefix.clone() + module_prefix; + std::borrow::Cow::Owned(combined_prefix) + } else { + std::borrow::Cow::Borrowed(*module_prefix) + }; + let func = + self.declare_import(module_prefix.as_ref(), name, &sig.params, &sig.results); // ... then call the function with all our operands if !sig.results.is_empty() { @@ -832,6 +943,10 @@ impl Bindgen for FunctionBindgen<'_, '_> { self.push_str("("); self.push_str(&operands.join(", ")); self.push_str(");\n"); + if self.r#gen.needs_deallocate { + self.push_str(&format!("for (ptr,layout) in _deallocate.drain(..) {{ _rt::alloc::dealloc(ptr, layout); }}\n")); + self.r#gen.needs_deallocate = false; + } } Instruction::CallInterface { func, async_ } => { @@ -915,7 +1030,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::AsyncTaskReturn { name, params } => { - let func = self.declare_import(name, params, &[]); + let func = self.declare_import("", name, params, &[]); uwriteln!(self.src, "_task_cancel.forget();"); uwriteln!(self.src, "{func}({});", operands.join(", ")); diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index 83e331aa0..595353b00 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -10,7 +10,8 @@ use std::fmt::Write as _; use std::mem; use wit_bindgen_core::abi::{self, AbiVariant, LiftLower}; use wit_bindgen_core::{ - dealias, uwrite, uwriteln, wit_parser::*, AnonymousTypeGenerator, Source, TypeInfo, + dealias, make_external_component, make_external_symbol, symmetric, uwrite, uwriteln, + wit_parser::*, AnonymousTypeGenerator, Source, TypeInfo, }; pub struct InterfaceGenerator<'a> { @@ -24,6 +25,7 @@ pub struct InterfaceGenerator<'a> { pub return_pointer_area_size: ArchitectureSize, pub return_pointer_area_align: Alignment, pub(super) needs_runtime_module: bool, + pub(super) needs_deallocate: bool, } /// A description of the "mode" in which a type is printed. @@ -196,50 +198,109 @@ impl<'i> InterfaceGenerator<'i> { } for (resource, (trait_name, methods)) in traits.iter() { - uwriteln!(self.src, "pub trait {trait_name}: 'static {{"); let resource = resource.unwrap(); let resource_name = self.resolve.types[resource].name.as_ref().unwrap(); + if self.gen.opts.symmetric { + let resource_type = resource_name.to_pascal_case(); + let resource_lowercase = resource_name.to_lower_camel_case(); + uwriteln!( + self.src, + r#"#[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_drop_{resource_lowercase}_cabi(arg0: usize) {{ + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + {resource_type}::dtor::(arg0 as *mut u8); + }}"# + ); + } + + uwriteln!(self.src, "pub trait {trait_name}: 'static {{"); let (_, interface_name) = interface.unwrap(); let module = self.resolve.name_world_key(interface_name); let wasm_import_module = format!("[export]{module}"); + let new_name = format!("[resource-new]{resource_name}"); + let external_new = + make_external_symbol(&wasm_import_module, &new_name, AbiVariant::GuestImport); + let rep_name = format!("[resource-rep]{resource_name}"); + let external_rep = + make_external_symbol(&wasm_import_module, &rep_name, AbiVariant::GuestImport); + let handle_type = if self.gen.opts.symmetric { + abi::WasmType::Pointer + } else { + abi::WasmType::I32 + }; let import_new = crate::declare_import( &wasm_import_module, - &format!("[resource-new]{resource_name}"), - "new", + &new_name, + &external_new, &[abi::WasmType::Pointer], - &[abi::WasmType::I32], + &[handle_type], ); let import_rep = crate::declare_import( &wasm_import_module, - &format!("[resource-rep]{resource_name}"), - "rep", - &[abi::WasmType::I32], + &rep_name, + &external_rep, + &[handle_type], &[abi::WasmType::Pointer], ); - uwriteln!( - self.src, - r#" + let handle_type = if self.gen.opts.symmetric { + "usize" + } else { + "u32" + }; + let casting = if self.gen.opts.symmetric { + " as *mut u8" + } else { + " as i32" + }; + if self.gen.opts.symmetric { + uwriteln!( + self.src, + r#" +#[doc(hidden)] +unsafe fn _resource_new(val: *mut u8) -> {handle_type} + where Self: Sized +{{ + val as {handle_type} +}} + +#[doc(hidden)] +fn _resource_rep(handle: {handle_type}) -> *mut u8 + where Self: Sized +{{ + handle as *mut u8 +}} + + "# + ); + } else { + uwriteln!( + self.src, + r#" #[doc(hidden)] -unsafe fn _resource_new(val: *mut u8) -> u32 +unsafe fn _resource_new(val: *mut u8) -> {handle_type} where Self: Sized {{ {import_new} - unsafe {{ new(val) as u32 }} + unsafe {{ {external_new}(val) as {handle_type} }} }} #[doc(hidden)] -fn _resource_rep(handle: u32) -> *mut u8 +fn _resource_rep(handle: {handle_type}) -> *mut u8 where Self: Sized {{ {import_rep} - unsafe {{ rep(handle as i32) }} + unsafe {{ {external_rep}(handle{casting}) }} }} "# - ); + ); + } for method in methods { self.src.push_str(method); } + uwriteln!(self.src, "}}"); } @@ -303,14 +364,37 @@ macro_rules! {macro_name} {{ } }; let camel = name.to_upper_camel_case(); - uwriteln!( - self.src, - r#" + if self.gen.opts.symmetric { + let dtor_symbol = make_external_symbol( + &module, + &(String::from("[resource-drop]") + &name), + AbiVariant::GuestImport, + ); + let resource_lowercase = name.to_lower_camel_case(); + uwriteln!( + self.src, + r#" #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + #[allow(non_snake_case)] + unsafe extern "C" fn {dtor_symbol}(arg0: usize) {{ + $($path_to_types)*::_export_drop_{resource_lowercase}_cabi::<<$ty as $($path_to_types)*::Guest>::{camel}>(arg0) + }} +"# + ); + } else { + let dtor_symbol = make_external_symbol( + &module, + &(String::from("[dtor]") + &name), + AbiVariant::GuestExport, + ); + uwriteln!( + self.src, + r#" const _: () = {{ #[doc(hidden)] - #[unsafe(export_name = "{export_prefix}{module}#[dtor]{name}")] + #[cfg_attr(target_arch = "wasm32", export_name = "{export_prefix}{module}#[dtor]{name}")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] #[allow(non_snake_case)] - unsafe extern "C" fn dtor(rep: *mut u8) {{ + unsafe extern "C" fn {dtor_symbol}(rep: *mut u8) {{ unsafe {{ $($path_to_types)*::{camel}::dtor::< <$ty as $($path_to_types)*::Guest>::{camel} @@ -319,7 +403,8 @@ macro_rules! {macro_name} {{ }} }}; "# - ); + ); + } } uwriteln!(self.src, "}};);"); uwriteln!(self.src, "}}"); @@ -551,8 +636,6 @@ macro_rules! {macro_name} {{ Alignment::default(), ) }; - let size = size.size_wasm32(); - let align = align.align_wasm32(); let lift; let lower; let dealloc_lists; @@ -588,12 +671,12 @@ macro_rules! {macro_name} {{ format!("unsafe fn dealloc_lists(ptr: *mut u8) {{ {dealloc_lists} }}"); let mut lift_arg = "lift"; let mut lower_arg = "lower"; - let mut dealloc_lists_arg = "dealloc_lists"; + let mut dealloc_lists_arg = "dealloc_lists,"; if let PayloadFor::Stream = payload_for { lift_arg = "lift: Some(lift)"; lower_arg = "lower: Some(lower)"; - dealloc_lists_arg = "dealloc_lists: Some(dealloc_lists)"; + dealloc_lists_arg = "dealloc_lists: Some(dealloc_lists),"; let is_list_canonical = match payload_type { Some(ty) => self.is_list_canonical(ty), @@ -603,19 +686,32 @@ macro_rules! {macro_name} {{ if is_list_canonical { lift_arg = "lift: None"; lower_arg = "lower: None"; - dealloc_lists_arg = "dealloc_lists: None"; + dealloc_lists_arg = "dealloc_lists: None,"; lift_fn = String::new(); lower_fn = String::new(); dealloc_lists_fn = String::new(); } } - - let code = format!( + let mut vtable_part1 = r#" + cancel_write, + cancel_read, + drop_writable, + drop_readable,"#; + let mut vtable_part2 = r#" + new, + start_read, + start_write,"#; + + let mut code = format!( r#" #[doc(hidden)] #[allow(unused_unsafe)] pub mod vtable{ordinal} {{ - +"# + ); + if !self.gen.opts.symmetric { + code.push_str(&format!( + r#" #[cfg(not(target_arch = "wasm32"))] unsafe extern "C" fn cancel_write(_: u32) -> u32 {{ unreachable!() }} #[cfg(not(target_arch = "wasm32"))] @@ -649,25 +745,27 @@ pub mod vtable{ordinal} {{ #[link_name = "[async-lower][{import_prefix}-write-{index}]{func_name}"] fn start_write(_: u32, _: *const u8{start_extra}) -> u32; }} - +"# + )); + } else { + dealloc_lists_arg = ""; + vtable_part1 = ""; + vtable_part2 = ""; + } + code.push_str(&format!(r#" {lift_fn} {lower_fn} {dealloc_lists_fn} pub static VTABLE: {async_support}::{camel}Vtable<{name}> = {async_support}::{camel}Vtable::<{name}> {{ - cancel_write, - cancel_read, - drop_writable, - drop_readable, - {dealloc_lists_arg}, + {vtable_part1} + {dealloc_lists_arg} layout: unsafe {{ ::std::alloc::Layout::from_size_align_unchecked({size}, {align}) }}, {lift_arg}, {lower_arg}, - new, - start_read, - start_write, + {vtable_part2} }}; impl super::{camel}Payload for {name} {{ @@ -675,7 +773,9 @@ pub mod vtable{ordinal} {{ }} }} "#, - ); + size = size.format_term(POINTER_SIZE_EXPRESSION, true), + align = align.format(POINTER_SIZE_EXPRESSION), + )); let map = match payload_for { PayloadFor::Future => &mut self.r#gen.future_payloads, @@ -706,6 +806,16 @@ pub mod vtable{ordinal} {{ self.src.push_str("#[allow(unused_unsafe, clippy::all)]\n"); let params = self.print_signature(func, async_, &sig); self.src.push_str("{\n"); + if self.gen.opts.symmetric + && symmetric::has_non_canonical_list_rust(self.resolve, &func.params) + { + self.needs_deallocate = true; + let _ = self.path_to_std_alloc_module(); + uwriteln!( + self.src, + "let mut _deallocate: Vec<(*mut u8, _rt::alloc::Layout)> = Vec::new();" + ); + } self.src.push_str("unsafe {\n"); if async_ { @@ -768,7 +878,11 @@ pub mod vtable{ordinal} {{ abi::call( f.r#gen.resolve, AbiVariant::GuestImport, - LiftLower::LowerArgsLiftResults, + if f.gen.gen.opts.symmetric { + LiftLower::Symmetric + } else { + LiftLower::LowerArgsLiftResults + }, func, &mut f, false, @@ -808,9 +922,11 @@ pub mod vtable{ordinal} {{ ) { let param_tys = func.params.iter().map(|(_, ty)| *ty).collect::>(); let async_support = self.r#gen.async_support_path(); - let sig = self - .resolve - .wasm_signature(AbiVariant::GuestImportAsync, func); + let sig = self.resolve.wasm_signature_symmetric( + AbiVariant::GuestImportAsync, + func, + self.r#gen.opts.symmetric, + ); uwriteln!( self.src, " @@ -1055,7 +1171,11 @@ unsafe fn call_import(_params: Self::ParamsLower, _results: *mut u8) -> u32 {{ abi::call( f.r#gen.resolve, variant, - LiftLower::LiftArgsLowerResults, + if f.gen.gen.opts.symmetric { + LiftLower::Symmetric + } else { + LiftLower::LiftArgsLowerResults + }, func, &mut f, async_, @@ -1104,7 +1224,9 @@ unsafe fn call_import(_params: Self::ParamsLower, _results: *mut u8) -> u32 {{ }} " ); - } else if abi::guest_export_needs_post_return(self.resolve, func) { + } else if abi::guest_export_needs_post_return(self.resolve, func) + && !self.gen.opts.symmetric + { uwrite!( self.src, "\ @@ -1145,18 +1267,40 @@ unsafe fn call_import(_params: Self::ParamsLower, _results: *mut u8) -> u32 {{ Identifier::StreamOrFuturePayload => unreachable!(), }; let export_prefix = self.r#gen.opts.export_prefix.as_deref().unwrap_or(""); - let export_name = func.legacy_core_export_name(wasm_module_export_name.as_deref()); - let export_name = if async_ { - format!("[async-lift]{export_name}") + // let mut library_name = String::new(); + let (export_name, external_name) = if self.r#gen.opts.symmetric { + let export_name = func.name.clone(); // item_name().to_owned(); + let mut external_name = make_external_symbol( + &wasm_module_export_name.unwrap_or_default(), + &func.name, + AbiVariant::GuestImport, + ); + if let Some(export_prefix) = self.r#gen.opts.export_prefix.as_ref() { + external_name.insert_str(0, export_prefix); + } + // if let Some(library) = &self.r#gen.opts.link_name { + // library_name = format!("\n#[link(name = \"{}\")]", library); + // } + (export_name, external_name) } else { - export_name.to_string() + let export_name = func.legacy_core_export_name(wasm_module_export_name.as_deref()); + let export_name = if async_ { + format!("[async-lift]{export_name}") + } else { + export_name.to_string() + }; + let external_name = + make_external_component(&(String::from(export_prefix) + &export_name)); + (export_name, external_name) }; uwrite!( self.src, "\ - #[unsafe(export_name = \"{export_prefix}{export_name}\")] - unsafe extern \"C\" fn export_{name_snake}\ -", + #[cfg_attr(target_arch = \"wasm32\", export_name = \"{export_prefix}{export_name}\")] + #[cfg_attr(not(target_arch = \"wasm32\"), no_mangle)] + #[allow(non_snake_case)] + unsafe extern \"C\" fn {external_name}\ + ", ); let params = self.print_export_sig(func, async_); @@ -1181,13 +1325,21 @@ unsafe fn call_import(_params: Self::ParamsLower, _results: *mut u8) -> u32 {{ }} " ); - } else if abi::guest_export_needs_post_return(self.resolve, func) { + } else if abi::guest_export_needs_post_return(self.resolve, func) + && !self.gen.opts.symmetric + { + let export_prefix = self.gen.opts.export_prefix.as_deref().unwrap_or(""); + let external_name = make_external_component(export_prefix) + + "cabi_post_" + + &make_external_component(&export_name); uwrite!( self.src, "\ - #[unsafe(export_name = \"{export_prefix}cabi_post_{export_name}\")] - unsafe extern \"C\" fn _post_return_{name_snake}\ -" + #[cfg_attr(target_arch = \"wasm32\", export_name = \"{export_prefix}cabi_post_{export_name}\")] + #[cfg_attr(not(target_arch = \"wasm32\"), no_mangle)] + #[allow(non_snake_case)] + unsafe extern \"C\" fn {external_name}\ + " ); let params = self.print_post_return_sig(func); self.src.push_str("{\n"); @@ -1207,7 +1359,8 @@ unsafe fn call_import(_params: Self::ParamsLower, _results: *mut u8) -> u32 {{ } else { AbiVariant::GuestExport }; - let sig = self.resolve.wasm_signature(variant, func); + let sig = + abi::wasm_signature_symmetric(self.resolve, variant, func, self.gen.opts.symmetric); let mut params = Vec::new(); for (i, param) in sig.params.iter().enumerate() { let name = format!("arg{}", i); @@ -2564,23 +2717,28 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { impl {camel} {{ #[doc(hidden)] - pub unsafe fn from_handle(handle: u32) -> Self {{ + pub unsafe fn from_handle(handle: {handle_type}) -> Self {{ Self {{ handle: unsafe {{ {resource}::from_handle(handle) }}, }} }} #[doc(hidden)] - pub fn take_handle(&self) -> u32 {{ + pub fn take_handle(&self) -> {handle_type} {{ {resource}::take_handle(&self.handle) }} #[doc(hidden)] - pub fn handle(&self) -> u32 {{ + pub fn handle(&self) -> {handle_type} {{ {resource}::handle(&self.handle) }} }} - "# + "#, + handle_type = if self.gen.opts.symmetric { + "usize" + } else { + "u32" + } ); self.wasm_import_module.to_string() } else { @@ -2637,19 +2795,19 @@ impl {camel} {{ }} #[doc(hidden)] - pub unsafe fn from_handle(handle: u32) -> Self {{ + pub unsafe fn from_handle(handle: {handle_type}) -> Self {{ Self {{ handle: unsafe {{ {resource}::from_handle(handle) }}, }} }} #[doc(hidden)] - pub fn take_handle(&self) -> u32 {{ + pub fn take_handle(&self) -> {handle_type} {{ {resource}::take_handle(&self.handle) }} #[doc(hidden)] - pub fn handle(&self) -> u32 {{ + pub fn handle(&self) -> {handle_type} {{ {resource}::handle(&self.handle) }} @@ -2713,17 +2871,33 @@ impl<'a> {camel}Borrow<'a>{{ self.rep.cast() }} }} - "# + "#, + handle_type = if self.gen.opts.symmetric { + "usize" + } else { + "u32" + } ); - format!("[export]{module}") + if self.gen.opts.symmetric { + module.clone() + } else { + format!("[export]{module}") + } }; let wasm_resource = self.path_to_wasm_resource(); + let drop_name = format!("[resource-drop]{name}"); + let export_name = + make_external_symbol(&wasm_import_module, &drop_name, AbiVariant::GuestImport); let intrinsic = crate::declare_import( &wasm_import_module, - &format!("[resource-drop]{name}"), - "drop", - &[abi::WasmType::I32], + &drop_name, + &export_name, + &[if self.gen.opts.symmetric { + abi::WasmType::Pointer + } else { + abi::WasmType::I32 + }], &[], ); uwriteln!( @@ -2731,12 +2905,22 @@ impl<'a> {camel}Borrow<'a>{{ r#" unsafe impl {wasm_resource} for {camel} {{ #[inline] - unsafe fn drop(_handle: u32) {{ - {intrinsic} - unsafe {{ drop(_handle as i32); }} + unsafe fn drop(_handle: {handle_type}) {{ + {intrinsic} + unsafe {{ {export_name}(_handle{casting}) }}; }} }} - "# + "#, + handle_type = if self.gen.opts.symmetric { + "usize" + } else { + "u32" + }, + casting = if self.gen.opts.symmetric { + " as *mut u8" + } else { + " as i32" + }, ); } diff --git a/crates/rust/src/lib.rs b/crates/rust/src/lib.rs index ee03aa7a1..75e4c84a8 100644 --- a/crates/rust/src/lib.rs +++ b/crates/rust/src/lib.rs @@ -25,6 +25,12 @@ struct InterfaceName { path: String, } +#[derive(Eq, Hash, PartialEq, Clone, Copy, Debug)] +enum Direction { + Import, + Export, +} + #[derive(Default)] struct RustWasm { types: Types, @@ -50,6 +56,10 @@ struct RustWasm { /// Maps wit interface and type names to their Rust identifiers with: GenerationConfiguration, + // needed for symmetric disambiguation + interface_prefixes: HashMap<(Direction, WorldKey), String>, + import_prefix: Option, + future_payloads: IndexMap, stream_payloads: IndexMap, } @@ -258,10 +268,27 @@ pub struct Opts { #[cfg_attr(feature = "clap", arg(long))] pub pub_export_macro: bool, + /// Generate code for 64bit wasm + #[cfg_attr(feature = "clap", arg(long))] + pub wasm64: bool, + /// Whether to generate unused structures, not generated by default (false) #[cfg_attr(feature = "clap", arg(long))] pub generate_unused_types: bool, + /// Symmetric ABI, this enables to directly link components to each + /// other and removes the primary distinction between host and guest. + #[cfg_attr(feature = "clap", arg(long, default_value_t = bool::default()))] + pub symmetric: bool, + + /// Library to link to for imports + #[cfg_attr(feature = "clap", arg(long))] + pub link_name: Option, + + /// Flip import and export on world (used for symmetric testing) + #[cfg_attr(feature = "clap", arg(long, default_value_t = bool::default()))] + pub invert_direction: bool, + /// Whether or not to generate helper function/constants to help link custom /// sections into the final output. /// @@ -296,7 +323,11 @@ impl RustWasm { resolve: &'a Resolve, in_import: bool, ) -> InterfaceGenerator<'a> { - let mut sizes = SizeAlign::default(); + let mut sizes = if self.opts.symmetric { + SizeAlign::new_symmetric() + } else { + SizeAlign::default() + }; sizes.fill(resolve); InterfaceGenerator { @@ -310,6 +341,7 @@ impl RustWasm { return_pointer_area_size: Default::default(), return_pointer_area_align: Default::default(), needs_runtime_module: false, + needs_deallocate: false, } } @@ -436,15 +468,20 @@ impl RustWasm { if !self.future_payloads.is_empty() { let async_support = self.async_support_path(); + let vtable_def = format!( + " + const VTABLE: &'static {async_support}::FutureVtable; + " + ); + let construct = + format!("unsafe {{ {async_support}::future_new::(default, T::VTABLE) }}"); self.src.push_str(&format!( "\ pub mod wit_future {{ #![allow(dead_code, unused_variables, clippy::all)] #[doc(hidden)] - pub trait FuturePayload: Unpin + Sized + 'static {{ - const VTABLE: &'static {async_support}::FutureVtable; - }}" + pub trait FuturePayload: Unpin + Sized + 'static {{{vtable_def}}}" )); for code in self.future_payloads.values() { self.src.push_str(code); @@ -456,7 +493,7 @@ pub mod wit_future {{ /// The `default` function provided computes the default value to be sent in /// this future if no other value was otherwise sent. pub fn new(default: fn() -> T) -> ({async_support}::FutureWriter, {async_support}::FutureReader) {{ - unsafe {{ {async_support}::future_new::(default, T::VTABLE) }} + {construct} }} }} ", @@ -465,14 +502,23 @@ pub mod wit_future {{ if !self.stream_payloads.is_empty() { let async_support = self.async_support_path(); + let vtable_def = format!( + " + const VTABLE: &'static {async_support}::StreamVtable; + " + ); + let construct = if self.opts.symmetric { + // no unsafe needed + format!("{async_support}::stream_new::(T::VTABLE)") + } else { + format!("unsafe {{ {async_support}::stream_new::(T::VTABLE) }}") + }; self.src.push_str(&format!( "\ pub mod wit_stream {{ #![allow(dead_code, unused_variables, clippy::all)] - pub trait StreamPayload: Unpin + Sized + 'static {{ - const VTABLE: &'static {async_support}::StreamVtable; - }}" + pub trait StreamPayload: Unpin + Sized + 'static {{{vtable_def}}}" )); for code in self.stream_payloads.values() { self.src.push_str(code); @@ -481,7 +527,7 @@ pub mod wit_stream {{ &format!("\ /// Creates a new Component Model `stream` with the specified payload type. pub fn new() -> ({async_support}::StreamWriter, {async_support}::StreamReader) {{ - unsafe {{ {async_support}::stream_new::(T::VTABLE) }} + {construct} }} }} "), @@ -620,12 +666,12 @@ pub fn run_ctors_once() {{ } RuntimeItem::ResourceType => { - self.src.push_str( + self.src.push_str(&format!( r#" use core::fmt; use core::marker; -use core::sync::atomic::{AtomicU32, Ordering::Relaxed}; +use core::sync::atomic::{{{atomic_type}, Ordering::Relaxed}}; /// A type which represents a component model resource, either imported or /// exported into this component. @@ -640,36 +686,36 @@ use core::sync::atomic::{AtomicU32, Ordering::Relaxed}; /// This type is primarily used in generated code for exported and imported /// resources. #[repr(transparent)] -pub struct Resource { - // NB: This would ideally be `u32` but it is not. The fact that this has +pub struct Resource {{ + // NB: This would ideally be `{handle_type}` but it is not. The fact that this has // interior mutability is not exposed in the API of this type except for the // `take_handle` method which is supposed to in theory be private. // // This represents, almost all the time, a valid handle value. When it's - // invalid it's stored as `u32::MAX`. - handle: AtomicU32, + // invalid it's stored as `{invalid_value}`. + handle: {atomic_type}, _marker: marker::PhantomData, -} +}} /// A trait which all wasm resources implement, namely providing the ability to /// drop a resource. /// /// This generally is implemented by generated code, not user-facing code. #[allow(clippy::missing_safety_doc)] -pub unsafe trait WasmResource { +pub unsafe trait WasmResource {{ /// Invokes the `[resource-drop]...` intrinsic. - unsafe fn drop(handle: u32); -} + unsafe fn drop(handle: {handle_type}); +}} -impl Resource { +impl Resource {{ #[doc(hidden)] - pub unsafe fn from_handle(handle: u32) -> Self { - debug_assert!(handle != 0 && handle != u32::MAX); - Self { - handle: AtomicU32::new(handle), + pub unsafe fn from_handle(handle: {handle_type}) -> Self {{ + debug_assert!(handle != 0 && handle != {invalid_value}); + Self {{ + handle: {atomic_type}::new(handle), _marker: marker::PhantomData, - } - } + }} + }} /// Takes ownership of the handle owned by `resource`. /// @@ -684,41 +730,48 @@ impl Resource { /// `take_handle` should only be exposed internally to generated code, not /// to user code. #[doc(hidden)] - pub fn take_handle(resource: &Resource) -> u32 { - resource.handle.swap(u32::MAX, Relaxed) - } + pub fn take_handle(resource: &Resource) -> {handle_type} {{ + resource.handle.swap({invalid_value}, Relaxed) + }} #[doc(hidden)] - pub fn handle(resource: &Resource) -> u32 { + pub fn handle(resource: &Resource) -> {handle_type} {{ resource.handle.load(Relaxed) - } -} + }} +}} -impl fmt::Debug for Resource { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl fmt::Debug for Resource {{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {{ f.debug_struct("Resource") .field("handle", &self.handle) .finish() - } -} + }} +}} -impl Drop for Resource { - fn drop(&mut self) { - unsafe { - match self.handle.load(Relaxed) { +impl Drop for Resource {{ + fn drop(&mut self) {{ + unsafe {{ + match self.handle.load(Relaxed) {{ // If this handle was "taken" then don't do anything in the // destructor. - u32::MAX => {} + {invalid_value} => {{}} // ... but otherwise do actually destroy it with the imported // component model intrinsic as defined through `T`. other => T::drop(other), - } - } - } -} + }} + }} + }} +}} "#, - ); + atomic_type = if self.opts.symmetric { + "AtomicUsize" + } else { + "AtomicU32" + }, + invalid_value = if self.opts.symmetric { "0" } else { "u32::MAX" }, + handle_type = if self.opts.symmetric { "usize" } else { "u32" } + )); } } } @@ -1091,6 +1144,12 @@ impl WorldGenerator for RustWasm { id: InterfaceId, _files: &mut Files, ) -> Result<()> { + if let Some(prefix) = self + .interface_prefixes + .get(&(Direction::Import, name.clone())) + { + self.import_prefix = Some(prefix.clone()); + } let mut to_define = Vec::new(); for (name, ty_id) in resolve.interfaces[id].types.iter() { let full_name = full_wit_type_name(resolve, *ty_id); @@ -1128,6 +1187,7 @@ impl WorldGenerator for RustWasm { r#gen.finish_append_submodule(&snake, module_path, docs); + let _ = self.import_prefix.take(); Ok(()) } @@ -1155,6 +1215,14 @@ impl WorldGenerator for RustWasm { id: InterfaceId, _files: &mut Files, ) -> Result<()> { + let old_prefix = self.opts.export_prefix.clone(); + if let Some(prefix) = self + .interface_prefixes + .get(&(Direction::Export, name.clone())) + { + self.opts.export_prefix = + Some(prefix.clone() + old_prefix.as_ref().unwrap_or(&String::new())); + } let mut to_define = Vec::new(); for (name, ty_id) in resolve.interfaces[id].types.iter() { let full_name = full_wit_type_name(resolve, *ty_id); @@ -1207,6 +1275,7 @@ impl WorldGenerator for RustWasm { let stub = r#gen.finish(); self.src.push_str(&stub); } + self.opts.export_prefix = old_prefix; Ok(()) } @@ -1383,6 +1452,46 @@ impl WorldGenerator for RustWasm { Ok(()) } + + fn apply_resolve_options(&mut self, resolve: &mut Resolve, world: &mut WorldId) { + if self.opts.invert_direction { + resolve.invert_direction(*world); + } + if self.opts.symmetric { + let world = &resolve.worlds[*world]; + let exports: HashMap<&WorldKey, &WorldItem> = world.exports.iter().collect(); + for (key, _item) in world.imports.iter() { + // duplicate found + if exports.contains_key(key) + && !self + .interface_prefixes + .contains_key(&(Direction::Import, key.clone())) + && !self + .interface_prefixes + .contains_key(&(Direction::Export, key.clone())) + { + self.interface_prefixes.insert( + (Direction::Import, key.clone()), + (if self.opts.invert_direction { + "exp_" + } else { + "imp_" + }) + .into(), + ); + self.interface_prefixes.insert( + (Direction::Export, key.clone()), + (if self.opts.invert_direction { + "imp_" + } else { + "exp_" + }) + .into(), + ); + } + } + } + } } fn compute_module_path(name: &WorldKey, resolve: &Resolve, is_export: bool) -> Vec { @@ -1619,6 +1728,7 @@ fn wasm_type(ty: WasmType) -> &'static str { } fn declare_import( + // module_prefix: &str, wasm_import_module: &str, wasm_import_name: &str, rust_name: &str, @@ -1639,15 +1749,12 @@ fn declare_import( } format!( " - #[cfg(target_arch = \"wasm32\")] #[link(wasm_import_module = \"{wasm_import_module}\")] unsafe extern \"C\" {{ - #[link_name = \"{wasm_import_name}\"] + #[allow(non_snake_case)] + #[cfg_attr(target_arch = \"wasm32\", link_name = \"{wasm_import_name}\")] fn {rust_name}{sig}; }} - - #[cfg(not(target_arch = \"wasm32\"))] - unsafe extern \"C\" fn {rust_name}{sig} {{ unreachable!() }} " ) } diff --git a/crates/symmetric_executor/Cargo.lock b/crates/symmetric_executor/Cargo.lock new file mode 100644 index 000000000..203befb4d --- /dev/null +++ b/crates/symmetric_executor/Cargo.lock @@ -0,0 +1,465 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "dummy-rt" +version = "0.1.0" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.167" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mini-bindgen" +version = "0.1.0" +dependencies = [ + "dummy-rt", + "wit-bindgen", + "wit-bindgen-symmetric-rt", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "prettyplease" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "symmetric_executor" +version = "0.1.0" +dependencies = [ + "dummy-rt", + "futures", + "libc", +] + +[[package]] +name = "symmetric_stream" +version = "0.1.0" +dependencies = [ + "dummy-rt", + "symmetric_executor", + "wit-bindgen-symmetric-rt", +] + +[[package]] +name = "syn" +version = "2.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "wasm-encoder" +version = "0.234.0" +source = "git+https://github.com/cpetig/wasm-tools?branch=symmetric#b975a3ac3e4a94b538bfe8cbd3edcad243aba36a" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.234.0" +source = "git+https://github.com/cpetig/wasm-tools?branch=symmetric#b975a3ac3e4a94b538bfe8cbd3edcad243aba36a" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.234.0" +source = "git+https://github.com/cpetig/wasm-tools?branch=symmetric#b975a3ac3e4a94b538bfe8cbd3edcad243aba36a" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver", +] + +[[package]] +name = "wit-bindgen" +version = "0.42.1" +dependencies = [ + "wit-bindgen-rt", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.42.1" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.42.1" +dependencies = [ + "bitflags", + "futures", + "once_cell", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.42.1" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.42.1" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-bindgen-symmetric-rt" +version = "0.36.0" +dependencies = [ + "bitflags", + "dummy-rt", + "futures", +] + +[[package]] +name = "wit-component" +version = "0.234.0" +source = "git+https://github.com/cpetig/wasm-tools?branch=symmetric#b975a3ac3e4a94b538bfe8cbd3edcad243aba36a" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.234.0" +source = "git+https://github.com/cpetig/wasm-tools?branch=symmetric#b975a3ac3e4a94b538bfe8cbd3edcad243aba36a" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] diff --git a/crates/symmetric_executor/Cargo.toml b/crates/symmetric_executor/Cargo.toml new file mode 100644 index 000000000..e31980e6d --- /dev/null +++ b/crates/symmetric_executor/Cargo.toml @@ -0,0 +1,26 @@ +[workspace] +package.version = "0.1.0" +package.edition = "2021" +members = [ "dummy-rt","symmetric_stream","rust-client","dummy-bindgen" ] + +[package] +name = "symmetric_executor" +edition.workspace = true +version.workspace = true + +[dependencies] +futures = "0.3.31" +libc = "0.2.167" + +[dependencies.wit-bindgen] +package = "dummy-rt" +path = "dummy-rt" + +[lib] +crate-type = ["cdylib"] + +[features] +# always off feature +never = [] +# output debugging information +trace = [] diff --git a/crates/symmetric_executor/cpp-client/.gitignore b/crates/symmetric_executor/cpp-client/.gitignore new file mode 100644 index 000000000..198698796 --- /dev/null +++ b/crates/symmetric_executor/cpp-client/.gitignore @@ -0,0 +1,2 @@ +/*.o +/libruntime.a diff --git a/crates/symmetric_executor/cpp-client/Makefile b/crates/symmetric_executor/cpp-client/Makefile new file mode 100644 index 000000000..a49f0a34b --- /dev/null +++ b/crates/symmetric_executor/cpp-client/Makefile @@ -0,0 +1,9 @@ +CXXFLAGS=-I../../cpp/helper-types -g -fPIC + +all: libruntime.a + +libruntime.a: module.o + ${AR} rcuvs $@ $^ + +clean: + -rm module.o libruntime.a diff --git a/crates/symmetric_executor/cpp-client/async_support.h b/crates/symmetric_executor/cpp-client/async_support.h new file mode 100644 index 000000000..6d83a4f59 --- /dev/null +++ b/crates/symmetric_executor/cpp-client/async_support.h @@ -0,0 +1,225 @@ + +#include +#include "module_cpp.h" +#include "stream_support.h" + +static inline symmetric::runtime::symmetric_executor::CallbackState fulfil_promise_void(void* data) { + std::unique_ptr> ptr((std::promise*)data); + ptr->set_value(); + return symmetric::runtime::symmetric_executor::CallbackState::kReady; +} + +static inline std::future lift_event(void* event) { + std::promise result; + std::future result1 = result.get_future(); + if (!event) { + result.set_value(); + } else { + std::unique_ptr> ptr = std::make_unique>(std::move(result)); + symmetric::runtime::symmetric_executor::EventSubscription ev = symmetric::runtime::symmetric_executor::EventSubscription(wit::ResourceImportBase((uint8_t*)event)); + symmetric::runtime::symmetric_executor::CallbackFunction fun = symmetric::runtime::symmetric_executor::CallbackFunction(wit::ResourceImportBase((uint8_t*)fulfil_promise_void)); + symmetric::runtime::symmetric_executor::CallbackData data = symmetric::runtime::symmetric_executor::CallbackData(wit::ResourceImportBase((uint8_t*)ptr.release())); + symmetric::runtime::symmetric_executor::Register(std::move(ev), std::move(fun), std::move(data)); + } + return result1; +} + +static inline symmetric::runtime::symmetric_executor::CallbackState wait_on_future(std::future* fut) { + fut->get(); + delete fut; + return symmetric::runtime::symmetric_executor::CallbackState::kReady; +} + +// static void run_in_background() { + +// } + +template +void* lower_async(std::future &&result1, std::function &&lower_result) { + if (result1.wait_for(std::chrono::seconds::zero()) == std::future_status::ready) { + lower_result(result1.get()); + return nullptr; + } else { + // move to run_in_background + symmetric::runtime::symmetric_executor::EventGenerator gen; + auto waiting = gen.Subscribe(); + auto task = std::async(std::launch::async, [lower_result](std::future&& result1, + symmetric::runtime::symmetric_executor::EventGenerator &&gen){ + lower_result(result1.get()); + gen.Activate(); + }, std::move(result1), std::move(gen)); + auto fut = std::make_unique>(std::move(task)); + symmetric::runtime::symmetric_executor::Register(waiting.Dup(), + symmetric::runtime::symmetric_executor::CallbackFunction(wit::ResourceImportBase((uint8_t*)wait_on_future)), + symmetric::runtime::symmetric_executor::CallbackData(wit::ResourceImportBase((uint8_t*)fut.release()))); + return waiting.into_handle(); + } +} + +template +union MaybeUninit { + T value; + char dummy; + MaybeUninit() + : dummy() + { } + ~MaybeUninit() + { } + MaybeUninit(const MaybeUninit &) = delete; + // assume that value isn't valid yet + MaybeUninit(MaybeUninit &&b) : dummy() { } +}; +template +struct fulfil_promise_data { + symmetric::runtime::symmetric_stream::StreamObj stream; + std::promise promise; + uint8_t value[LIFT::SIZE]; +}; + +template +static symmetric::runtime::symmetric_executor::CallbackState fulfil_promise(void* data) { + std::unique_ptr> ptr((fulfil_promise_data*)data); + auto buffer = ptr->stream.ReadResult(); + ptr->promise.set_value(LIFT::lift(ptr->value)); + return symmetric::runtime::symmetric_executor::CallbackState::kReady; +} + +template +std::future lift_future(uint8_t* stream) { + std::promise promise; + std::future result= promise.get_future(); + auto stream2 = symmetric::runtime::symmetric_stream::StreamObj(wit::ResourceImportBase(stream)); + auto event = stream2.ReadReadySubscribe(); + std::unique_ptr> data = std::make_unique>(fulfil_promise_data{std::move(stream2), std::move(promise), {0}}); + symmetric::runtime::symmetric_stream::Buffer buf = symmetric::runtime::symmetric_stream::Buffer( + symmetric::runtime::symmetric_stream::Address(wit::ResourceImportBase((wit::ResourceImportBase::handle_t)&data->value)), + 1 + ); + data->stream.StartReading(std::move(buf)); + symmetric::runtime::symmetric_executor::Register(std::move(event), + symmetric::runtime::symmetric_executor::CallbackFunction(wit::ResourceImportBase((uint8_t*)&fulfil_promise)), + symmetric::runtime::symmetric_executor::CallbackData(wit::ResourceImportBase((uint8_t*)data.release()))); + return result; +} + +template +wit::stream lift_stream(uint8_t* stream) { + return wit::stream{symmetric::runtime::symmetric_stream::StreamObj(wit::ResourceImportBase((wit::ResourceImportBase::handle_t)stream))}; +} + +template struct future_writer { + symmetric::runtime::symmetric_stream::StreamObj handle; +}; + +template struct future_reader { + symmetric::runtime::symmetric_stream::StreamObj handle; +}; + +template +std::pair, future_reader> create_wasi_future() { + auto stream = symmetric::runtime::symmetric_stream::StreamObj(); + auto stream2 = stream.Clone(); + return std::make_pair, future_reader>( + future_writer{std::move(stream)}, future_reader{std::move(stream2)}); +} + +template struct stream_writer { + symmetric::runtime::symmetric_stream::StreamObj handle; + + void write(std::vector&& data) { + while (!data.empty()) { + if (!handle.IsReadyToWrite()) { + abort(); + // symmetric::runtime::symmetric_executor::BlockOn(handle.WriteReadySubscribe()); + } + auto buffer = handle.StartWriting(); + auto capacity = buffer.Capacity(); + uint8_t* dest = (uint8_t*)buffer.GetAddress().into_handle(); + auto elements = data.size(); + if (elements::lower(std::move(data[i]), dest+(i*wit::StreamProperties::lowered_size)); + } + buffer.SetSize(elements); + handle.FinishWriting(std::optional(std::move(buffer))); + + if (capacity>data.size()) capacity = data.size(); + data.erase(data.begin(), data.begin() + capacity); + } + } + ~stream_writer() { + if (handle.get_handle()!=wit::ResourceImportBase::invalid) { + handle.FinishWriting(std::optional()); + } + } + stream_writer(const stream_writer&) = delete; + stream_writer& operator=(const stream_writer&) = delete; + stream_writer(stream_writer&&) = default; + stream_writer& operator=(stream_writer&&) = default; +}; + +template +std::pair, wit::stream> create_wasi_stream() { + auto stream = symmetric::runtime::symmetric_stream::StreamObj(); + auto stream2 = stream.Clone(); + return std::make_pair, wit::stream>( + stream_writer{std::move(stream)}, wit::stream{std::move(stream2)}); +} + +template +struct write_to_future_data { + future_writer wr; + std::future fut; +}; + +template +static symmetric::runtime::symmetric_executor::CallbackState write_to_future(void* data) { + std::unique_ptr> ptr((write_to_future_data*)data); + // is future ready? + if (ptr->fut.wait_for(std::chrono::seconds::zero()) == std::future_status::ready) { + auto buffer = ptr->wr.handle.StartWriting(); + assert(buffer.Capacity()==1); + uint8_t* dataptr = (uint8_t*)(buffer.GetAddress().into_handle()); + auto result = ptr->fut.get(); + LOWER::lower(std::move(result), dataptr); + buffer.SetSize(1); + ptr->wr.handle.FinishWriting(std::optional(std::move(buffer))); + } else { + // sadly there is no easier way to wait for a future in the background? + // move to run_in_background + symmetric::runtime::symmetric_executor::EventGenerator gen; + auto waiting = gen.Subscribe(); + auto task = std::async(std::launch::async, [](std::unique_ptr> &&ptr, + symmetric::runtime::symmetric_executor::EventGenerator &&gen){ + auto buffer = ptr->wr.handle.StartWriting(); + // assert(buffer.GetSize()==1); //sizeof(T)); + uint8_t* dataptr = (uint8_t*)(buffer.GetAddress().into_handle()); + auto result = ptr->fut.get(); + LOWER::lower(std::move(result), dataptr); + buffer.SetSize(1); + ptr->wr.handle.FinishWriting(std::optional(std::move(buffer))); + gen.Activate(); + }, std::move(ptr), std::move(gen)); + auto fut = std::make_unique>(std::move(task)); + symmetric::runtime::symmetric_executor::Register(waiting.Dup(), + symmetric::runtime::symmetric_executor::CallbackFunction(wit::ResourceImportBase((uint8_t*)wait_on_future)), + symmetric::runtime::symmetric_executor::CallbackData(wit::ResourceImportBase((uint8_t*)fut.release()))); + } + return symmetric::runtime::symmetric_executor::CallbackState::kReady; +} + +template +uint8_t* lower_future(std::future &&f) { + auto handles = create_wasi_future(); + auto wait_on = handles.first.handle.WriteReadySubscribe(); + auto fut = std::make_unique>(write_to_future_data{std::move(handles.first), std::move(f)}); + symmetric::runtime::symmetric_executor::Register(std::move(wait_on), + symmetric::runtime::symmetric_executor::CallbackFunction(wit::ResourceImportBase((uint8_t*)&write_to_future)), + symmetric::runtime::symmetric_executor::CallbackData(wit::ResourceImportBase((uint8_t*)fut.release()))); + return handles.second.handle.into_handle(); +} + +template +uint8_t* lower_stream(wit::stream &&f) { + return f.handle.into_handle(); +} diff --git a/crates/symmetric_executor/cpp-client/generate.sh b/crates/symmetric_executor/cpp-client/generate.sh new file mode 100755 index 000000000..099a2d3c2 --- /dev/null +++ b/crates/symmetric_executor/cpp-client/generate.sh @@ -0,0 +1 @@ +../../../target/debug/wit-bindgen cpp ../wit -w module --symmetric --new-api diff --git a/crates/symmetric_executor/cpp-client/module.cpp b/crates/symmetric_executor/cpp-client/module.cpp new file mode 100644 index 000000000..ae22d4e74 --- /dev/null +++ b/crates/symmetric_executor/cpp-client/module.cpp @@ -0,0 +1,261 @@ +// Generated by `wit-bindgen` 0.3.0. DO NOT EDIT! + +// Ensure that the *_component_type.o object is linked in +#ifdef __wasm32__ +extern void __component_type_object_force_link_module(void); +void __component_type_object_force_link_module_public_use_in_this_compilation_unit(void) { + __component_type_object_force_link_module(); +} +#endif +#include "module_cpp.h" +#include // realloc + +extern "C" void *cabi_realloc(void *ptr, size_t old_size, size_t align, size_t new_size); + +__attribute__((__weak__, __export_name__("cabi_realloc"))) +void *cabi_realloc(void *ptr, size_t old_size, size_t align, size_t new_size) { + (void) old_size; + if (new_size == 0) return (void*) align; + void *ret = realloc(ptr, new_size); + if (!ret) abort(); + return ret; +} + + +extern "C" void symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Dcallback_function(uint8_t*); +extern "C" void symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Dcallback_data(uint8_t*); +extern "C" void symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Devent_subscription(uint8_t*); +extern "C" int32_t symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_subscriptionX2Eready(uint8_t*); +extern "C" uint8_t* symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BstaticX5Devent_subscriptionX2Efrom_timeout(int64_t); +extern "C" uint8_t* symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_subscriptionX2Edup(uint8_t*); +extern "C" void symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_subscriptionX2Ereset(uint8_t*); +extern "C" void symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Devent_generator(uint8_t*); +extern "C" uint8_t* symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BconstructorX5Devent_generator(); +extern "C" uint8_t* symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_generatorX2Esubscribe(uint8_t*); +extern "C" void symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_generatorX2Eactivate(uint8_t*); +extern "C" void symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Dcallback_registration(uint8_t*); +extern "C" uint8_t* symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BstaticX5Dcallback_registrationX2Ecancel(uint8_t*); +extern "C" void symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00run(); +extern "C" uint8_t* symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00register(uint8_t*, uint8_t*, uint8_t*); +extern "C" void symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5Bresource_dropX5Daddress(uint8_t*); +extern "C" void symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5Bresource_dropX5Dbuffer(uint8_t*); +extern "C" uint8_t* symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BconstructorX5Dbuffer(uint8_t*, int64_t); +extern "C" uint8_t* symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5DbufferX2Eget_address(uint8_t*); +extern "C" int64_t symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5DbufferX2Eget_size(uint8_t*); +extern "C" void symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5DbufferX2Eset_size(uint8_t*, int64_t); +extern "C" int64_t symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5DbufferX2Ecapacity(uint8_t*); +extern "C" void symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5Bresource_dropX5Dstream_obj(uint8_t*); +extern "C" uint8_t* symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BconstructorX5Dstream_obj(); +extern "C" uint8_t* symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eclone(uint8_t*); +extern "C" int32_t symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eis_write_closed(uint8_t*); +extern "C" void symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Estart_reading(uint8_t*, uint8_t*); +extern "C" void symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Ewrite_ready_activate(uint8_t*); +extern "C" uint8_t* symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eread_ready_subscribe(uint8_t*); +extern "C" void symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eread_result(uint8_t*, uint8_t*); +extern "C" int32_t symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eis_ready_to_write(uint8_t*); +extern "C" uint8_t* symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Ewrite_ready_subscribe(uint8_t*); +extern "C" uint8_t* symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Estart_writing(uint8_t*); +extern "C" void symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Efinish_writing(uint8_t*, int32_t, uint8_t*); +extern "C" void symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eread_ready_activate(uint8_t*); +symmetric::runtime::symmetric_executor::CallbackFunction::~CallbackFunction() +{ + if (handle!=nullptr) { + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Dcallback_function(handle); + } +} +symmetric::runtime::symmetric_executor::CallbackFunction::CallbackFunction(wit::ResourceImportBase&&b) : wit::ResourceImportBase(std::move(b)) {} +symmetric::runtime::symmetric_executor::CallbackData::~CallbackData() +{ + if (handle!=nullptr) { + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Dcallback_data(handle); + } +} +symmetric::runtime::symmetric_executor::CallbackData::CallbackData(wit::ResourceImportBase&&b) : wit::ResourceImportBase(std::move(b)) {} +symmetric::runtime::symmetric_executor::EventSubscription::~EventSubscription() +{ + if (handle!=nullptr) { + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Devent_subscription(handle); + } +} +bool symmetric::runtime::symmetric_executor::EventSubscription::Ready() const +{ + auto ret = symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_subscriptionX2Eready((*this).get_handle()); + return (bool(ret)); +} +symmetric::runtime::symmetric_executor::EventSubscription symmetric::runtime::symmetric_executor::EventSubscription::FromTimeout(uint64_t nanoseconds) +{ + auto ret = symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BstaticX5Devent_subscriptionX2Efrom_timeout((int64_t(nanoseconds))); + return wit::ResourceImportBase{ret}; +} +symmetric::runtime::symmetric_executor::EventSubscription symmetric::runtime::symmetric_executor::EventSubscription::Dup() const +{ + auto ret = symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_subscriptionX2Edup((*this).get_handle()); + return wit::ResourceImportBase{ret}; +} +void symmetric::runtime::symmetric_executor::EventSubscription::Reset() const +{ + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_subscriptionX2Ereset((*this).get_handle()); +} +symmetric::runtime::symmetric_executor::EventSubscription::EventSubscription(wit::ResourceImportBase&&b) : wit::ResourceImportBase(std::move(b)) {} +symmetric::runtime::symmetric_executor::EventGenerator::~EventGenerator() +{ + if (handle!=nullptr) { + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Devent_generator(handle); + } +} +symmetric::runtime::symmetric_executor::EventGenerator::EventGenerator() +{ + auto ret = symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BconstructorX5Devent_generator(); + this->handle = wit::ResourceImportBase{ret}.into_handle(); +} +symmetric::runtime::symmetric_executor::EventSubscription symmetric::runtime::symmetric_executor::EventGenerator::Subscribe() const +{ + auto ret = symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_generatorX2Esubscribe((*this).get_handle()); + return wit::ResourceImportBase{ret}; +} +void symmetric::runtime::symmetric_executor::EventGenerator::Activate() const +{ + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_generatorX2Eactivate((*this).get_handle()); +} +symmetric::runtime::symmetric_executor::EventGenerator::EventGenerator(wit::ResourceImportBase&&b) : wit::ResourceImportBase(std::move(b)) {} +symmetric::runtime::symmetric_executor::CallbackRegistration::~CallbackRegistration() +{ + if (handle!=nullptr) { + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Dcallback_registration(handle); + } +} +symmetric::runtime::symmetric_executor::CallbackData symmetric::runtime::symmetric_executor::CallbackRegistration::Cancel(CallbackRegistration&& obj) +{ + auto ret = symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BstaticX5Dcallback_registrationX2Ecancel(obj.into_handle()); + return wit::ResourceImportBase{ret}; +} +symmetric::runtime::symmetric_executor::CallbackRegistration::CallbackRegistration(wit::ResourceImportBase&&b) : wit::ResourceImportBase(std::move(b)) {} +void symmetric::runtime::symmetric_executor::Run() +{ + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00run(); +} +symmetric::runtime::symmetric_executor::CallbackRegistration symmetric::runtime::symmetric_executor::Register(EventSubscription&& trigger, CallbackFunction&& callback, CallbackData&& data) +{ + auto ret = symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00register(trigger.into_handle(), callback.into_handle(), data.into_handle()); + return wit::ResourceImportBase{ret}; +} +symmetric::runtime::symmetric_stream::Address::~Address() +{ + if (handle!=nullptr) { + symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5Bresource_dropX5Daddress(handle); + } +} +symmetric::runtime::symmetric_stream::Address::Address(wit::ResourceImportBase&&b) : wit::ResourceImportBase(std::move(b)) {} +symmetric::runtime::symmetric_stream::Buffer::~Buffer() +{ + if (handle!=nullptr) { + symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5Bresource_dropX5Dbuffer(handle); + } +} +symmetric::runtime::symmetric_stream::Buffer::Buffer(Address&& addr, uint64_t capacity) +{ + auto ret = symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BconstructorX5Dbuffer(addr.into_handle(), (int64_t(capacity))); + this->handle = wit::ResourceImportBase{ret}.into_handle(); +} +symmetric::runtime::symmetric_stream::Address symmetric::runtime::symmetric_stream::Buffer::GetAddress() const +{ + auto ret = symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5DbufferX2Eget_address((*this).get_handle()); + return wit::ResourceImportBase{ret}; +} +uint64_t symmetric::runtime::symmetric_stream::Buffer::GetSize() const +{ + auto ret = symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5DbufferX2Eget_size((*this).get_handle()); + return (uint64_t(ret)); +} +void symmetric::runtime::symmetric_stream::Buffer::SetSize(uint64_t size) const +{ + symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5DbufferX2Eset_size((*this).get_handle(), (int64_t(size))); +} +uint64_t symmetric::runtime::symmetric_stream::Buffer::Capacity() const +{ + auto ret = symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5DbufferX2Ecapacity((*this).get_handle()); + return (uint64_t(ret)); +} +symmetric::runtime::symmetric_stream::Buffer::Buffer(wit::ResourceImportBase&&b) : wit::ResourceImportBase(std::move(b)) {} +symmetric::runtime::symmetric_stream::StreamObj::~StreamObj() +{ + if (handle!=nullptr) { + symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5Bresource_dropX5Dstream_obj(handle); + } +} +symmetric::runtime::symmetric_stream::StreamObj::StreamObj() +{ + auto ret = symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BconstructorX5Dstream_obj(); + this->handle = wit::ResourceImportBase{ret}.into_handle(); +} +symmetric::runtime::symmetric_stream::StreamObj symmetric::runtime::symmetric_stream::StreamObj::Clone() const +{ + auto ret = symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eclone((*this).get_handle()); + return wit::ResourceImportBase{ret}; +} +bool symmetric::runtime::symmetric_stream::StreamObj::IsWriteClosed() const +{ + auto ret = symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eis_write_closed((*this).get_handle()); + return (bool(ret)); +} +void symmetric::runtime::symmetric_stream::StreamObj::StartReading(Buffer&& buffer) const +{ + symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Estart_reading((*this).get_handle(), buffer.into_handle()); +} +void symmetric::runtime::symmetric_stream::StreamObj::WriteReadyActivate() const +{ + symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Ewrite_ready_activate((*this).get_handle()); +} +symmetric::runtime::symmetric_executor::EventSubscription symmetric::runtime::symmetric_stream::StreamObj::ReadReadySubscribe() const +{ + auto ret = symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eread_ready_subscribe((*this).get_handle()); + return wit::ResourceImportBase{ret}; +} +std::optional symmetric::runtime::symmetric_stream::StreamObj::ReadResult() const +{ + uintptr_t ret_area[((2*sizeof(void*))+sizeof(uintptr_t)-1)/sizeof(uintptr_t)]; + uint8_t* ptr0 = (uint8_t*)(&ret_area); + symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eread_result((*this).get_handle(), ptr0); + std::optional option1; + if ((int32_t) (*((uint8_t*) (ptr0 + 0)))) { + + option1.emplace(wit::ResourceImportBase{*((uint8_t**) (ptr0 + sizeof(void*)))}); + } + return (option1); +} +bool symmetric::runtime::symmetric_stream::StreamObj::IsReadyToWrite() const +{ + auto ret = symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eis_ready_to_write((*this).get_handle()); + return (bool(ret)); +} +symmetric::runtime::symmetric_executor::EventSubscription symmetric::runtime::symmetric_stream::StreamObj::WriteReadySubscribe() const +{ + auto ret = symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Ewrite_ready_subscribe((*this).get_handle()); + return wit::ResourceImportBase{ret}; +} +symmetric::runtime::symmetric_stream::Buffer symmetric::runtime::symmetric_stream::StreamObj::StartWriting() const +{ + auto ret = symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Estart_writing((*this).get_handle()); + return wit::ResourceImportBase{ret}; +} +void symmetric::runtime::symmetric_stream::StreamObj::FinishWriting(std::optional &&buffer) const +{ + int32_t option2; + uint8_t* option3; + if ((buffer).has_value()) { + Buffer payload1 = (std::move(buffer)).value(); + option2 = (int32_t(1)); + option3 = payload1.into_handle(); + } else { + option2 = (int32_t(0)); + option3 = nullptr; + } + symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Efinish_writing((*this).get_handle(), option2, option3); +} +void symmetric::runtime::symmetric_stream::StreamObj::ReadReadyActivate() const +{ + symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eread_ready_activate((*this).get_handle()); +} +symmetric::runtime::symmetric_stream::StreamObj::StreamObj(wit::ResourceImportBase&&b) : wit::ResourceImportBase(std::move(b)) {} + +// Component Adapters diff --git a/crates/symmetric_executor/cpp-client/module_cpp.h b/crates/symmetric_executor/cpp-client/module_cpp.h new file mode 100644 index 000000000..5ce0087bc --- /dev/null +++ b/crates/symmetric_executor/cpp-client/module_cpp.h @@ -0,0 +1,141 @@ +// Generated by `wit-bindgen` 0.3.0. DO NOT EDIT! +#ifndef __CPP_GUEST_BINDINGS_MODULE_H +#define __CPP_GUEST_BINDINGS_MODULE_H +#define WIT_SYMMETRIC +#include +#include +#include +#include +#include +namespace symmetric {namespace runtime {namespace symmetric_executor {/// These pseudo-resources are just used to +/// pass pointers to register +/// Return value of an event callback +enum class CallbackState : uint8_t { + /// Call the function again + kPending = 0, + /// The function has completed, all results are written, data is freed, + /// calling the function again is not permitted as data became invalid! + kReady = 1, +}; + +class CallbackFunction : public wit::ResourceImportBase{ + + public: + + ~CallbackFunction(); + CallbackFunction(wit::ResourceImportBase &&); + CallbackFunction(CallbackFunction&&) = default; + CallbackFunction& operator=(CallbackFunction&&) = default; +}; + +class CallbackData : public wit::ResourceImportBase{ + + public: + + ~CallbackData(); + CallbackData(wit::ResourceImportBase &&); + CallbackData(CallbackData&&) = default; + CallbackData& operator=(CallbackData&&) = default; +}; + +class EventSubscription : public wit::ResourceImportBase{ + + public: + + ~EventSubscription(); + bool Ready() const; + static EventSubscription FromTimeout(uint64_t nanoseconds); + EventSubscription Dup() const; + void Reset() const; + EventSubscription(wit::ResourceImportBase &&); + EventSubscription(EventSubscription&&) = default; + EventSubscription& operator=(EventSubscription&&) = default; +}; + +class EventGenerator : public wit::ResourceImportBase{ + + public: + + ~EventGenerator(); + EventGenerator(); + EventSubscription Subscribe() const; + void Activate() const; + EventGenerator(wit::ResourceImportBase &&); + EventGenerator(EventGenerator&&) = default; + EventGenerator& operator=(EventGenerator&&) = default; +}; + +class CallbackRegistration : public wit::ResourceImportBase{ + + public: + + ~CallbackRegistration(); + static CallbackData Cancel(CallbackRegistration&& obj); + CallbackRegistration(wit::ResourceImportBase &&); + CallbackRegistration(CallbackRegistration&&) = default; + CallbackRegistration& operator=(CallbackRegistration&&) = default; +}; + +/// Return value of an async call, lowest bit encoding +enum class CallStatus : uint8_t { + /// For symmetric this means that processing has started, parameters should still remain valid until null, + /// params-read = non-null, results-written,done = null + kStarted = 0, + /// For symmetric: Retry the call (temporarily out of memory) + kNotStarted = 1, +}; + +void Run(); +CallbackRegistration Register(EventSubscription&& trigger, CallbackFunction&& callback, CallbackData&& data); +} +namespace symmetric_stream {using EventSubscription = symmetric_executor::EventSubscription; +class Address : public wit::ResourceImportBase{ + + public: + + ~Address(); + Address(wit::ResourceImportBase &&); + Address(Address&&) = default; + Address& operator=(Address&&) = default; +}; + +class Buffer : public wit::ResourceImportBase{ + + public: + + ~Buffer(); + Buffer(Address&& addr, uint64_t capacity); + Address GetAddress() const; + uint64_t GetSize() const; + void SetSize(uint64_t size) const; + uint64_t Capacity() const; + Buffer(wit::ResourceImportBase &&); + Buffer(Buffer&&) = default; + Buffer& operator=(Buffer&&) = default; +}; + +class StreamObj : public wit::ResourceImportBase{ + + public: + + ~StreamObj(); + StreamObj(); + StreamObj Clone() const; + bool IsWriteClosed() const; + void StartReading(Buffer&& buffer) const; + void WriteReadyActivate() const; + symmetric_executor::EventSubscription ReadReadySubscribe() const; + std::optional ReadResult() const; + bool IsReadyToWrite() const; + symmetric_executor::EventSubscription WriteReadySubscribe() const; + Buffer StartWriting() const; + void FinishWriting(std::optional &&buffer) const; + void ReadReadyActivate() const; + StreamObj(wit::ResourceImportBase &&); + StreamObj(StreamObj&&) = default; + StreamObj& operator=(StreamObj&&) = default; +}; + +}}} + +#endif diff --git a/crates/symmetric_executor/cpp-client/stream_support.h b/crates/symmetric_executor/cpp-client/stream_support.h new file mode 100644 index 000000000..871fc54a3 --- /dev/null +++ b/crates/symmetric_executor/cpp-client/stream_support.h @@ -0,0 +1,103 @@ +#pragma once +#ifndef WIT_STREAM_SUPPORT_H +#define WIT_STREAM_SUPPORT_H + +#include "module_cpp.h" +#include + +namespace wit { + // template + // union MaybeUninit + // { + // T value; + // char dummy; + // MaybeUninit() + // : dummy() + // { } + // MaybeUninit(MaybeUninit const& b) + // : dummy() + // { } + // ~MaybeUninit() + // { } + // }; + + template + struct StreamProperties { + static const uint32_t lowered_size; + static T lift(uint8_t const*); + static void lower(T&&, uint8_t*); + }; + + template struct stream { + symmetric::runtime::symmetric_stream::StreamObj handle; + // StreamProperties const* lifting; + + uint32_t buffer_size = 1; + + static stream new_empty() { + return stream{symmetric::runtime::symmetric_stream::StreamObj(wit::ResourceImportBase()), 1}; + } + + struct background_object { + symmetric::runtime::symmetric_stream::StreamObj handle; + std::function)> reader; + std::vector buffer; + // StreamProperties const* lifting; + + background_object(symmetric::runtime::symmetric_stream::StreamObj && h, + std::function)>&& r, std::vector &&b) + : handle(std::move(h)), reader(std::move(r)), buffer(std::move(b)) {} + }; + + stream& buffering(uint32_t amount) { + buffer_size = amount; + return *this; + } + static symmetric::runtime::symmetric_executor::CallbackState data_available(background_object* data) { + auto buffer = data->handle.ReadResult(); + if (buffer.has_value()) { + assert(buffer->GetAddress().into_handle() == (wit::ResourceImportBase::handle_t)data->buffer.data()); + uint32_t size = buffer->GetSize(); + std::vector lifted; + lifted.reserve(size); + for (uint32_t i = 0; i::lift(data->buffer.data()+i*StreamProperties::lowered_size)); + } + if (size>0) + data->reader(wit::span(lifted.data(), size)); + // if closed we won't get another notification + if (data->handle.IsWriteClosed()) { + data->reader(wit::span(nullptr, 0)); + auto release = std::unique_ptr(data); + return symmetric::runtime::symmetric_executor::CallbackState::kReady; + } else { + data->handle.StartReading(std::move(*buffer)); + return symmetric::runtime::symmetric_executor::CallbackState::kPending; + } + } else { + data->reader(wit::span(nullptr, 0)); + auto release = std::unique_ptr(data); + return symmetric::runtime::symmetric_executor::CallbackState::kReady; + } + } + void set_reader(std::function)> &&fun) && { + std::vector buffer(buffer_size*StreamProperties::lowered_size, uint8_t(0)); + background_object* object = + std::make_unique(background_object{std::move(handle), std::move(fun), std::move(buffer)}).release(); + + symmetric::runtime::symmetric_stream::Buffer b( + symmetric::runtime::symmetric_stream::Address(wit::ResourceImportBase{(wit::ResourceImportBase::handle_t)object->buffer.data()}), buffer_size); + + object->handle.StartReading(std::move(b)); + symmetric::runtime::symmetric_executor::Register(object->handle.ReadReadySubscribe(), + symmetric::runtime::symmetric_executor::CallbackFunction(wit::ResourceImportBase{(wit::ResourceImportBase::handle_t)&data_available}), + symmetric::runtime::symmetric_executor::CallbackData(wit::ResourceImportBase{(wit::ResourceImportBase::handle_t)object})); + } + stream(const stream&) = delete; + stream(stream&&) = default; + stream& operator=(const stream&) = delete; + stream& operator=(stream&&) = default; + }; +} + +#endif diff --git a/crates/symmetric_executor/dummy-bindgen/Cargo.toml b/crates/symmetric_executor/dummy-bindgen/Cargo.toml new file mode 100644 index 000000000..97551013c --- /dev/null +++ b/crates/symmetric_executor/dummy-bindgen/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "mini-bindgen" +version.workspace = true +edition.workspace = true + +[dependencies] +wit-bindgen-symmetric-rt = { path = "../rust-client", optional = true } +dummy-rt = { path = "../dummy-rt", optional = true } +original = { path = "../../guest-rust", optional = true, package = "wit-bindgen" } + +[features] +# no default gives you the original wit-bindgen crate with rt +default = [ "symmetric", "async" ] +symmetric = [ "dep:dummy-rt", "dep:wit-bindgen-symmetric-rt" ] +canonical = [ "dep:original" ] +async = [] diff --git a/crates/symmetric_executor/dummy-bindgen/src/lib.rs b/crates/symmetric_executor/dummy-bindgen/src/lib.rs new file mode 100644 index 000000000..019b5d86b --- /dev/null +++ b/crates/symmetric_executor/dummy-bindgen/src/lib.rs @@ -0,0 +1,17 @@ +// this crate tries to minimize dependencies for symmetric bindings + +#[cfg(feature = "symmetric")] +pub mod rt { + pub use dummy_rt::rt::maybe_link_cabi_realloc; + pub use wit_bindgen_symmetric_rt::{ + async_support, run, Cleanup, EventGenerator, EventSubscription, + }; +} + +#[cfg(feature = "canonical")] +pub use original::rt; + +#[cfg(feature = "async")] +pub use wit_bindgen_symmetric_rt::async_support::{ + block_on, spawn, FutureReader, FutureWriter, StreamReader, StreamResult, StreamWriter, +}; diff --git a/crates/symmetric_executor/dummy-rt/Cargo.toml b/crates/symmetric_executor/dummy-rt/Cargo.toml new file mode 100644 index 000000000..afebb3eee --- /dev/null +++ b/crates/symmetric_executor/dummy-rt/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "dummy-rt" +version.workspace = true +edition.workspace = true + +[dependencies] diff --git a/crates/symmetric_executor/dummy-rt/src/lib.rs b/crates/symmetric_executor/dummy-rt/src/lib.rs new file mode 100644 index 000000000..bf3a5a920 --- /dev/null +++ b/crates/symmetric_executor/dummy-rt/src/lib.rs @@ -0,0 +1,6 @@ +// this crate tries to minimize dependencies introduced by generated +// bindings + +pub mod rt { + pub fn maybe_link_cabi_realloc() {} +} diff --git a/crates/symmetric_executor/generate.sh b/crates/symmetric_executor/generate.sh new file mode 100755 index 000000000..ca5883e31 --- /dev/null +++ b/crates/symmetric_executor/generate.sh @@ -0,0 +1,4 @@ +#!/bin/sh +(cd rust-client/src;../../../../target/debug/wit-bindgen rust ../../wit -w module --symmetric --async none ; cd .. ; cargo fmt) +(cd src;../../../target/debug/wit-bindgen rust ../wit -w executor --symmetric --async none ; cd .. ; cargo fmt) +(cd symmetric_stream/src;../../../../target/debug/wit-bindgen rust ../../wit -w stream-impl --symmetric --async none ; cd .. ; cargo fmt) diff --git a/crates/symmetric_executor/rust-client/Cargo.lock b/crates/symmetric_executor/rust-client/Cargo.lock new file mode 100644 index 000000000..4bd09ef9d --- /dev/null +++ b/crates/symmetric_executor/rust-client/Cargo.lock @@ -0,0 +1,585 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "anyhow" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" +dependencies = [ + "backtrace", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "indexmap" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + +[[package]] +name = "libc" +version = "0.2.167" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "prettyplease" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "spdx" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae30cc7bfe3656d60ee99bf6836f472b0c53dddcbf335e253329abb16e535a2" +dependencies = [ + "smallvec", +] + +[[package]] +name = "syn" +version = "2.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "wasm-encoder" +version = "0.221.2" +source = "git+https://github.com/cpetig/wasm-tools?branch=symmetric#50a75a9c0690ad9b8280655253cf4b7b25b9c41e" +dependencies = [ + "leb128", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.221.2" +source = "git+https://github.com/cpetig/wasm-tools?branch=symmetric#50a75a9c0690ad9b8280655253cf4b7b25b9c41e" +dependencies = [ + "anyhow", + "indexmap", + "serde", + "serde_derive", + "serde_json", + "spdx", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.221.2" +source = "git+https://github.com/cpetig/wasm-tools?branch=symmetric#50a75a9c0690ad9b8280655253cf4b7b25b9c41e" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.36.0" +dependencies = [ + "wit-bindgen-rt", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.36.0" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.36.0" +dependencies = [ + "bitflags", + "futures", + "once_cell", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.36.0" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.36.0" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-bindgen-symmetric-rt" +version = "0.36.0" +dependencies = [ + "futures", + "wit-bindgen", +] + +[[package]] +name = "wit-component" +version = "0.221.2" +source = "git+https://github.com/cpetig/wasm-tools?branch=symmetric#50a75a9c0690ad9b8280655253cf4b7b25b9c41e" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.221.2" +source = "git+https://github.com/cpetig/wasm-tools?branch=symmetric#50a75a9c0690ad9b8280655253cf4b7b25b9c41e" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] diff --git a/crates/symmetric_executor/rust-client/Cargo.toml b/crates/symmetric_executor/rust-client/Cargo.toml new file mode 100644 index 000000000..100cd4d61 --- /dev/null +++ b/crates/symmetric_executor/rust-client/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "wit-bindgen-symmetric-rt" +version = "0.36.0" +edition = "2021" + +[dependencies] +bitflags = "2.9.1" +futures = "0.3.31" + +[dependencies.wit-bindgen] +package = "dummy-rt" +path = "../dummy-rt" + +[features] +# always off feature +never = [] +default = ["symmetric"] +symmetric = [] +bitflags = [] diff --git a/crates/symmetric_executor/rust-client/src/async_support.rs b/crates/symmetric_executor/rust-client/src/async_support.rs new file mode 100644 index 000000000..1cab79974 --- /dev/null +++ b/crates/symmetric_executor/rust-client/src/async_support.rs @@ -0,0 +1,202 @@ +use futures::{task::Waker, FutureExt}; +use std::{ + future::Future, + mem::MaybeUninit, + pin::Pin, + sync::{Arc, RwLock}, + task::{Context, Poll, RawWaker, RawWakerVTable}, +}; + +use crate::module::symmetric::runtime::symmetric_executor::{ + self, CallbackState, EventGenerator, EventSubscription, +}; + +pub use future_support::{future_new, FutureReader, FutureVtable, FutureWriter}; +pub use stream_support::{ + results, stream_new, Stream, StreamReader, StreamResult, StreamVtable, StreamWriter, +}; +pub use subtask::Subtask; + +// #[path = "../../../guest-rust/rt/src/async_support/abi_buffer.rs"] +// pub mod abi_buffer; +pub mod future_support; +pub mod rust_buffer; +pub mod stream_support; +mod subtask; +// #[path = "../../../guest-rust/rt/src/async_support/waitable.rs"] +// mod waitable; + +// See https://github.com/rust-lang/rust/issues/13231 for the limitation +// / Send constraint on futures for spawn, loosen later +// pub unsafe auto trait MaybeSend : Send {} +// unsafe impl MaybeSend for T where T: Send {} + +type BoxFuture = Pin + 'static>>; + +struct FutureState { + future: BoxFuture, + // signal to activate once the current async future has finished + completion_event: Option, + // the event this future should wake on + waiting_for: Option, +} + +static VTABLE: RawWakerVTable = RawWakerVTable::new( + |_| RawWaker::new(core::ptr::null(), &VTABLE), + // `wake` does nothing + |_| {}, + // `wake_by_ref` does nothing + |_| {}, + // Dropping does nothing as we don't allocate anything + |_| {}, +); + +pub fn new_waker(waiting_for_ptr: *mut Option) -> Waker { + unsafe { Waker::from_raw(RawWaker::new(waiting_for_ptr.cast(), &VTABLE)) } +} + +unsafe fn poll(state: *mut FutureState) -> Poll<()> { + let mut pinned = std::pin::pin!(&mut (*state).future); + let waker = new_waker(&mut (&mut *state).waiting_for as *mut Option); + pinned + .as_mut() + .poll(&mut Context::from_waker(&waker)) + .map(|()| { + let state_owned = Box::from_raw(state); + if let Some(waker) = &state_owned.completion_event { + waker.activate(); + } + drop(state_owned); + }) +} + +pub fn context_set_wait(cx: &Context, wait_for: &EventSubscription) { + // remember this eventsubscription in the context + let data = cx.waker().data(); + let mut copy = Some(wait_for.dup()); + std::mem::swap( + unsafe { &mut *(data.cast::>().cast_mut()) }, + &mut copy, + ); +} + +pub async fn wait_on(wait_for: EventSubscription) { + std::future::poll_fn(move |cx| { + if wait_for.ready() { + Poll::Ready(()) + } else { + context_set_wait(cx, &wait_for); + Poll::Pending + } + }) + .await +} + +extern "C" fn symmetric_callback(obj: *mut ()) -> CallbackState { + match unsafe { poll(obj.cast()) } { + Poll::Ready(_) => CallbackState::Ready, + Poll::Pending => { + let state = obj.cast::(); + if let Some(waiting_for) = unsafe { &mut *state }.waiting_for.take() { + super::register(waiting_for, symmetric_callback, obj); + } + // as we registered this callback on a new event stop calling + // from the old event + CallbackState::Ready + } + } +} + +pub fn first_poll_sub(future: BoxFuture) -> *mut () { + let state = Box::into_raw(Box::new(FutureState { + future, + completion_event: None, + waiting_for: None, + })); + match unsafe { poll(state) } { + Poll::Ready(()) => core::ptr::null_mut(), + Poll::Pending => { + if let Some(waiting_for) = unsafe { &mut *state }.waiting_for.take() { + let completion_event = EventGenerator::new(); + let wait_chain = completion_event.subscribe().take_handle() as *mut (); + unsafe { &mut *state } + .completion_event + .replace(completion_event); + super::register(waiting_for, symmetric_callback, state.cast()); + wait_chain + } else { + core::ptr::null_mut() + } + } + } +} + +/// Poll the future generated by a call to an async-lifted export once, calling +/// the specified closure (presumably backed by a call to `task.return`) when it +/// generates a value. +/// +/// This will return a non-null pointer representing the task if it hasn't +/// completed immediately; otherwise it returns null. +#[doc(hidden)] +pub fn first_poll( + future: impl Future + 'static, + fun: impl FnOnce(T) + 'static, +) -> *mut () { + first_poll_sub(Box::pin(future.map(fun))) +} + +/// Await the completion of a call to an async-lowered import. +#[doc(hidden)] +pub async unsafe fn await_result(function: impl Fn() -> *mut u8) { + let wait_for = function(); + if !wait_for.is_null() { + let wait_for = unsafe { EventSubscription::from_handle(wait_for as usize) }; + wait_on(wait_for).await; + } +} + +pub fn spawn(future: impl Future + 'static + Send) { + let wait_for = first_poll(future, |()| ()); + let wait_for = unsafe { EventSubscription::from_handle(wait_for as usize) }; + drop(wait_for); +} + +pub unsafe fn spawn_unchecked(future: impl Future) { + let future1: Pin>> = Box::pin(future); + let wait_for = first_poll_sub(unsafe { std::mem::transmute(future1) }); + if !wait_for.is_null() { + let wait_for = unsafe { EventSubscription::from_handle(wait_for as usize) }; + drop(wait_for); + } +} + +pub fn block_on(future: impl Future + 'static) -> T { + // ugly but might do the trick + let result: Arc>> = Arc::new(RwLock::new(MaybeUninit::uninit())); + let result2 = Arc::clone(&result); + let future2 = async move { + result2.write().unwrap().write(future.await); + }; + unsafe { spawn_unchecked(future2) }; + symmetric_executor::run(); + return unsafe { result.to_owned().write().unwrap().assume_init_read() }; +} + +pub struct TaskCancelOnDrop; + +impl TaskCancelOnDrop { + pub fn new() -> Self { + todo!(); + // Self + } + pub fn forget(self) {} +} + +pub fn start_task(future: impl Future) -> i32 { + unsafe { spawn_unchecked(future) }; + todo!() +} + +pub unsafe fn callback(_event0: u32, _event1: u32, _event2: u32) -> u32 { + todo!(); +} diff --git a/crates/symmetric_executor/rust-client/src/async_support/future_support.rs b/crates/symmetric_executor/rust-client/src/async_support/future_support.rs new file mode 100644 index 000000000..a105727cf --- /dev/null +++ b/crates/symmetric_executor/rust-client/src/async_support/future_support.rs @@ -0,0 +1,199 @@ +use std::{ + alloc::Layout, + future::{Future, IntoFuture}, + mem::MaybeUninit, + pin::Pin, + task::{Context, Poll}, +}; + +use futures::FutureExt; + +use crate::symmetric_stream::{Address, Buffer}; + +use super::{super::Cleanup, wait_on, Stream}; + +#[doc(hidden)] +pub struct FutureVtable { + pub layout: Layout, + pub lower: unsafe fn(value: T, dst: *mut u8), + pub lift: unsafe fn(dst: *mut u8) -> T, +} + +pub struct FutureWriter { + handle: Stream, + vtable: &'static FutureVtable, +} + +impl FutureWriter { + pub fn new(handle: Stream, vtable: &'static FutureVtable) -> Self { + Self { handle, vtable } + } + + pub fn write(self, data: T) -> FutureWrite { + FutureWrite { + writer: self, + future: None, + data: Some(data), + } + } +} + +/// Represents a write operation which may be canceled prior to completion. +pub struct FutureWrite { + writer: FutureWriter, + future: Option> + 'static + Send>>>, + data: Option, +} + +impl Future for FutureWrite { + type Output = Result<(), ()>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let me = self.get_mut(); + + if me.future.is_none() { + let handle = me.writer.handle.clone(); + let data = me.data.take().unwrap(); + let lower = me.writer.vtable.lower; + me.future = Some(Box::pin(async move { + if !handle.is_ready_to_write() { + let subsc = handle.write_ready_subscribe(); + wait_on(subsc).await; + } + let buffer = handle.start_writing(); + let addr = buffer.get_address().take_handle() as *mut MaybeUninit as *mut u8; + unsafe { (lower)(data, addr) }; + buffer.set_size(1); + handle.finish_writing(Some(buffer)); + Ok(()) + }) + as Pin + Send>>); + } + me.future.as_mut().unwrap().poll_unpin(cx) + } +} + +/// Represents a read operation which may be canceled prior to completion. +pub struct FutureRead { + reader: FutureReader, + future: Option + 'static + Send>>>, +} + +pub struct FutureReader { + handle: Stream, + vtable: &'static FutureVtable, +} + +impl FutureReader { + pub fn new(handle: *mut u8, vtable: &'static FutureVtable) -> Self { + Self { + handle: unsafe { Stream::from_handle(handle as usize) }, + vtable, + } + } + + pub fn read(self) -> FutureRead { + FutureRead { + reader: self, + future: None, + } + } + + pub unsafe fn from_handle(handle: *mut u8, vtable: &'static FutureVtable) -> Self { + Self::new(handle, vtable) + } + + pub fn take_handle(&self) -> *mut () { + self.handle.take_handle() as *mut () + } +} + +impl Future for FutureRead { + type Output = T; + + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + let me = self.get_mut(); + + if me.future.is_none() { + let handle = me.reader.handle.clone(); + let vtable = me.reader.vtable; + me.future = Some(Box::pin(async move { + // sadly there is no easy way to embed this in the future as the size is not accessible at compile time + let (buffer0, cleanup) = Cleanup::new(vtable.layout); + let address = unsafe { Address::from_handle(buffer0 as usize) }; + let buffer = Buffer::new(address, 1); + handle.start_reading(buffer); + let subsc = handle.read_ready_subscribe(); + subsc.reset(); + wait_on(subsc).await; + let buffer2 = handle.read_result(); + if let Some(buffer2) = buffer2 { + let count = buffer2.get_size(); + if count > 0 { + unsafe { (vtable.lift)(buffer2.get_address().take_handle() as *mut u8) } + } else { + // make sure it lives long enough + drop(cleanup); + todo!() + } + } else { + todo!() + } + }) as Pin + Send>>); + } + + match me.future.as_mut().unwrap().as_mut().poll(cx) { + Poll::Ready(v) => { + me.future = None; + Poll::Ready(v) + } + Poll::Pending => Poll::Pending, + } + } +} + +impl FutureRead { + pub fn cancel(mut self) -> FutureReader { + self.cancel_mut() + } + + fn cancel_mut(&mut self) -> FutureReader { + todo!() + } +} + +impl IntoFuture for FutureReader { + type Output = T; + type IntoFuture = FutureRead; + + /// Convert this object into a `Future` which will resolve when a value is + /// written to the writable end of this `future` (yielding a `Some` result) + /// or when the writable end is dropped (yielding a `None` result). + fn into_future(self) -> Self::IntoFuture { + self.read() + } +} + +pub fn new_future( + vtable: &'static FutureVtable, +) -> (FutureWriter, FutureReader) { + let handle = Stream::new(); + let handle2 = handle.clone(); + ( + FutureWriter::new(handle, vtable), + FutureReader::new(handle2.take_handle() as *mut u8, vtable), + ) +} + +pub unsafe fn future_new( + _default: fn() -> T, + vtable: &'static FutureVtable, +) -> (FutureWriter, FutureReader) { + new_future(vtable) + // let handle = Stream::new(); + // let handle2 = handle.clone(); + // ( + // FutureWriter::new(handle, vtable), + // FutureReader::new(handle2, vtable), + // ) +} diff --git a/crates/symmetric_executor/rust-client/src/async_support/rust_buffer.rs b/crates/symmetric_executor/rust-client/src/async_support/rust_buffer.rs new file mode 100644 index 000000000..886b0d48c --- /dev/null +++ b/crates/symmetric_executor/rust-client/src/async_support/rust_buffer.rs @@ -0,0 +1,26 @@ +// unlike abi_buffer this caches high level elements as we directly write into the +// read buffer and don't need a storage for lowered elements + +use std::collections::VecDeque; + +pub struct RustBuffer { + buf: VecDeque, +} + +// struct BufIterator<'a, T: 'static> { +// buf: &'a mut RustBuffer, +// } + +impl RustBuffer { + pub(crate) fn new(vec: Vec) -> Self { + Self { buf: vec.into() } + } + + pub fn remaining(&self) -> usize { + self.buf.len() + } + + pub(crate) fn drain_n(&mut self, n: usize) -> impl Iterator + use<'_, T> { + self.buf.drain(0..n) + } +} diff --git a/crates/symmetric_executor/rust-client/src/async_support/stream_support.rs b/crates/symmetric_executor/rust-client/src/async_support/stream_support.rs new file mode 100644 index 000000000..2f347b74f --- /dev/null +++ b/crates/symmetric_executor/rust-client/src/async_support/stream_support.rs @@ -0,0 +1,419 @@ +pub use crate::module::symmetric::runtime::symmetric_stream::StreamObj as Stream; +use crate::{ + async_support::{rust_buffer::RustBuffer, wait_on}, + symmetric_stream::{Address, Buffer}, +}; +use { + futures::sink::Sink, + std::{ + alloc::Layout, + convert::Infallible, + fmt, + future::Future, + iter, + marker::PhantomData, + mem::MaybeUninit, + pin::Pin, + task::{Context, Poll}, + }, +}; + +// waitable::{WaitableOp, WaitableOperation} looked cool, but +// as it waits for the runtime and uses Wakers I don't think the +// logic fits + +#[doc(hidden)] +pub struct StreamVtable { + pub layout: Layout, + pub lower: Option, + pub lift: Option T>, + //pub dealloc_lists: Option, +} + +// const fn ceiling(x: usize, y: usize) -> usize { +// (x / y) + if x % y == 0 { 0 } else { 1 } +// } + +pub mod results { + pub const BLOCKED: isize = -1; + pub const CLOSED: isize = isize::MIN; + pub const CANCELED: isize = 0; +} + +// Used within Waitable +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum StreamResult { + Complete(usize), + Dropped, + Cancelled, +} + +pub struct StreamWrite<'a, T: 'static> { + // op: WaitableOperation>, + _phantom: PhantomData<&'a T>, + writer: &'a mut StreamWriter, + _future: Option + 'static + Send>>>, + values: RustBuffer, +} + +impl Future for StreamWrite<'_, T> { + type Output = (StreamResult, RustBuffer); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let me = self.get_mut(); + match Pin::new(&mut me.writer).poll_ready(cx) { + Poll::Ready(_) => { + if me.values.remaining() == 0 { + // delayed flush + let values = RustBuffer::new(Vec::new()); + // std::mem::swap(&mut me.values, &mut values); + // I assume EOF + me.writer.handle.finish_writing(None); + Poll::Ready((StreamResult::Complete(0), values)) + } else { + // send data + // Pin::new(&mut me.writer).start_send(Vec::new()).unwrap(); + let buffer = me.writer.handle.start_writing(); + let addr = buffer.get_address().take_handle() as *mut u8; + let size = (buffer.capacity() as usize).min(me.values.remaining()); + let mut dest = addr; + if let Some(lower) = me.writer._vtable.lower { + for i in me.values.drain_n(size) { + unsafe { (lower)(i, dest) }; + dest = unsafe { dest.byte_add(me.writer._vtable.layout.size()) }; + } + } else { + todo!(); + } + buffer.set_size(size as u64); + me.writer.handle.finish_writing(Some(buffer)); + let mut values = RustBuffer::new(Vec::new()); + std::mem::swap(&mut me.values, &mut values); + Poll::Ready((StreamResult::Complete(size), values)) + } + } + Poll::Pending => Poll::Pending, + } + } +} + +pub struct StreamWriter { + handle: Stream, + /*?*/ future: Option + 'static + Send>>>, + _vtable: &'static StreamVtable, +} + +impl StreamWriter { + #[doc(hidden)] + pub fn new(handle: Stream, vtable: &'static StreamVtable) -> Self { + Self { + handle, + future: None, + _vtable: vtable, + } + } + + pub fn write(&mut self, values: Vec) -> StreamWrite<'_, T> { + self.write_buf(RustBuffer::new(values)) //, self._vtable)) + // StreamWrite { + // writer: self, + // _future: None, + // _phantom: PhantomData, + // values, + // } + } + + pub fn write_buf(&mut self, values: RustBuffer) -> StreamWrite<'_, T> { + StreamWrite { + writer: self, + _future: None, + _phantom: PhantomData, + values, + } + } + + pub fn cancel(&mut self) { + todo!() + } +} + +impl fmt::Debug for StreamWriter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("StreamWriter") + .field("handle", &self.handle) + .finish() + } +} + +impl Sink> for StreamWriter { + type Error = Infallible; + + fn poll_ready(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + let me = self.get_mut(); + + let ready = me.handle.is_ready_to_write(); + + // see also StreamReader::poll_next + if !ready && me.future.is_none() { + let handle = me.handle.clone(); + me.future = Some(Box::pin(async move { + let handle_local = handle; + let subscr = handle_local.write_ready_subscribe(); + subscr.reset(); + wait_on(subscr).await; + }) as Pin + Send>>); + } + + if let Some(future) = &mut me.future { + match future.as_mut().poll(cx) { + Poll::Ready(_) => { + me.future = None; + Poll::Ready(Ok(())) + } + Poll::Pending => Poll::Pending, + } + } else { + Poll::Ready(Ok(())) + } + } + + fn start_send(self: Pin<&mut Self>, _item: Vec) -> Result<(), Self::Error> { + todo!(); + // let item_len = item.len(); + // let me = self.get_mut(); + // let stream = &me.handle; + // let buffer = stream.start_writing(); + // let addr = buffer.get_address().take_handle() as *mut u8; + // let size = buffer.capacity() as usize; + // assert!(size >= item_len); + // let slice = + // unsafe { std::slice::from_raw_parts_mut(addr.cast::>(), item_len) }; + // for (a, b) in slice.iter_mut().zip(item.drain(..)) { + // // TODO: lower + // a.write(b); + // } + // buffer.set_size(item_len as u64); + // stream.finish_writing(Some(buffer)); + // Ok(()) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + self.poll_ready(cx) + } + + fn poll_close(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + self.poll_ready(cx) + } +} + +impl Drop for StreamWriter { + fn drop(&mut self) { + if !self.handle.is_write_closed() { + self.handle.finish_writing(None); + } + } +} + +/// Represents the readable end of a Component Model `stream`. +pub struct StreamReader { + handle: Stream, + future: Option>> + 'static + Send>>>, + _vtable: &'static StreamVtable, +} + +impl fmt::Debug for StreamReader { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("StreamReader") + .field("handle", &self.handle) + .finish() + } +} + +impl StreamReader { + #[doc(hidden)] + pub unsafe fn new(handle: *mut u8, vtable: &'static StreamVtable) -> Self { + Self { + handle: unsafe { Stream::from_handle(handle as usize) }, + future: None, + _vtable: vtable, + } + } + + pub unsafe fn from_handle(handle: *mut u8, vtable: &'static StreamVtable) -> Self { + Self::new(handle, vtable) + } + + /// Cancel the current pending read operation. + /// + /// This will panic if no such operation is pending. + pub fn cancel(&mut self) { + assert!(self.future.is_some()); + self.future = None; + } + + #[doc(hidden)] + pub fn take_handle(&self) -> usize { + self.handle.take_handle() + } + + #[doc(hidden)] + // remove this as it is weirder than take_handle + pub fn into_handle(self) -> *mut () { + self.handle.take_handle() as *mut () + } + + pub fn read(&mut self, buf: Vec) -> StreamRead<'_, T> { + StreamRead { + // marker: PhantomData, + reader: self, + buf, + } + } +} + +impl StreamReader { + pub async fn next(&mut self) -> Option { + let buf = self.read(Vec::with_capacity(1)).await; + buf.and_then(|mut v| v.pop()) + } +} + +// impl futures::stream::Stream for StreamReader { +// type Item = Vec; + +// fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { +// let me = self.get_mut(); + +// if me.future.is_none() { +// let handle = me.handle.clone(); +// me.future = Some(Box::pin(async move { +// let mut buffer0 = iter::repeat_with(MaybeUninit::uninit) +// .take(ceiling(4 * 1024, mem::size_of::())) +// .collect::>(); +// let address = unsafe { Address::from_handle(buffer0.as_mut_ptr() as usize) }; +// let buffer = Buffer::new(address, buffer0.len() as u64); +// handle.start_reading(buffer); +// let subsc = handle.read_ready_subscribe(); +// subsc.reset(); +// wait_on(subsc).await; +// let buffer2 = handle.read_result(); +// if let Some(buffer2) = buffer2 { +// let count = buffer2.get_size(); +// buffer0.truncate(count as usize); +// // TODO: lift +// Some(unsafe { mem::transmute::>, Vec>(buffer0) }) +// } else { +// None +// } +// }) as Pin + Send>>); +// } + +// match me.future.as_mut().unwrap().as_mut().poll(cx) { +// Poll::Ready(v) => { +// me.future = None; +// Poll::Ready(v) +// } +// Poll::Pending => Poll::Pending, +// } +// } +// } + +impl Drop for StreamReader { + fn drop(&mut self) { + if self.handle.handle() != 0 { + self.handle.write_ready_activate(); + } + } +} + +pub struct StreamRead<'a, T: 'static> { + // marker: PhantomData<(&'a mut StreamReader, T)>, + buf: Vec, + reader: &'a mut StreamReader, +} + +impl Future for StreamRead<'_, T> { + type Output = Option>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // TODO: Check whether WaitableOperation helps here + //self.pin_project().poll_complete(cx) + + // todo!() + + let me2 = self.get_mut(); + let me = &mut me2.reader; + + if me.future.is_none() { + if me.handle.is_write_closed() { + return Poll::Ready(None); + } + let mut buffer2 = Vec::new(); + std::mem::swap(&mut buffer2, &mut me2.buf); + let handle = me.handle.clone(); + let vtable = me._vtable; + me.future = Some(Box::pin(async move { + let mut buffer0: Vec> = iter::repeat_with(MaybeUninit::uninit) + .take(vtable.layout.size() * buffer2.capacity()) + .collect::>(); + let address = unsafe { Address::from_handle(buffer0.as_mut_ptr() as usize) }; + let buffer = Buffer::new(address, buffer2.capacity() as u64); + handle.start_reading(buffer); + let subsc = handle.read_ready_subscribe(); + subsc.reset(); + wait_on(subsc).await; + let buffer3 = handle.read_result(); + // let mut srcptr = buffer0.as_mut_ptr(); + if let Some(buffer3) = buffer3 { + let count = buffer3.get_size(); + let mut srcptr = buffer3.get_address().take_handle() as *mut u8; + if let Some(lift) = vtable.lift { + for _ in 0..count { + buffer2.push(unsafe { (lift)(srcptr) }); + srcptr = unsafe { srcptr.byte_add(vtable.layout.size()) }; + } + } else { + todo!() + } + //buffer0.truncate(count as usize); + Some(buffer2) + } else { + None + } + }) as Pin + Send>>); + } + + match me.future.as_mut().unwrap().as_mut().poll(cx) { + Poll::Ready(v) => { + me.future = None; + Poll::Ready(v) + } + Poll::Pending => Poll::Pending, + } + } +} + +// impl<'a, T> StreamRead<'a, T> { +// fn pin_project(self: Pin<&mut Self>) -> Pin<&mut WaitableOperation>> { +// // SAFETY: we've chosen that when `Self` is pinned that it translates to +// // always pinning the inner field, so that's codified here. +// unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().op) } +// } +// } + +/// deprecate this, replace with stream_new +pub fn new_stream( + vtable: &'static StreamVtable, +) -> (StreamWriter, StreamReader) { + let handle = Stream::new(); + let handle2 = handle.clone(); + (StreamWriter::new(handle, vtable), unsafe { + StreamReader::new(handle2.take_handle() as *mut u8, vtable) + }) +} + +pub fn stream_new( + vtable: &'static StreamVtable, +) -> (StreamWriter, StreamReader) { + new_stream(vtable) +} diff --git a/crates/symmetric_executor/rust-client/src/async_support/subtask.rs b/crates/symmetric_executor/rust-client/src/async_support/subtask.rs new file mode 100644 index 000000000..c9b70cc52 --- /dev/null +++ b/crates/symmetric_executor/rust-client/src/async_support/subtask.rs @@ -0,0 +1,22 @@ +use std::alloc::Layout; +use std::future::Future; + +// dummy to just make the generated code compile, for now +pub unsafe trait Subtask { + const ABI_LAYOUT: Layout; + const RESULTS_OFFSET: usize; + type Params; + type Results; + type ParamsLower: Copy; + unsafe fn call_import(params: Self::ParamsLower, results: *mut u8) -> u32; + unsafe fn params_lower(params: Self::Params, dst: *mut u8) -> Self::ParamsLower; + unsafe fn params_dealloc_lists(lower: Self::ParamsLower); + unsafe fn params_dealloc_lists_and_own(lower: Self::ParamsLower); + unsafe fn results_lift(src: *mut u8) -> Self::Results; + fn call(_params: Self::Params) -> impl Future + where + Self: Sized, + { + async { todo!() } + } +} diff --git a/crates/symmetric_executor/rust-client/src/lib.rs b/crates/symmetric_executor/rust-client/src/lib.rs new file mode 100644 index 000000000..48017ebfd --- /dev/null +++ b/crates/symmetric_executor/rust-client/src/lib.rs @@ -0,0 +1,91 @@ +use core::ptr::{self, NonNull}; +use module::symmetric::runtime::symmetric_executor::{self, CallbackData, CallbackFunction}; +pub use module::symmetric::runtime::symmetric_executor::{ + run, CallbackState, EventGenerator, EventSubscription, +}; +pub use module::symmetric::runtime::symmetric_stream; +use std::alloc::{self, Layout}; + +pub mod async_support; +mod module; + +// Re-export `bitflags` so that we can reference it from macros. +#[cfg(feature = "bitflags")] +#[doc(hidden)] +pub use bitflags; + +pub struct EventSubscription2; +pub struct EventGenerator2; + +pub fn register( + event: EventSubscription, + f: extern "C" fn(*mut T) -> CallbackState, + data: *mut T, +) { + let callback = unsafe { CallbackFunction::from_handle(f as *const () as usize) }; + let cb_data = unsafe { CallbackData::from_handle(data as usize) }; + symmetric_executor::register(event, callback, cb_data); +} + +// #[no_mangle] +// fn cabi_realloc_wit_bindgen_0_41_0( +// _old_ptr: *mut u8, +// _old_len: usize, +// _align: usize, +// _new_len: usize, +// ) -> *mut u8 { +// todo!() +// } + +pub unsafe fn subscribe_event_send_ptr(event_send: *mut EventGenerator2) -> EventSubscription { + let gener: EventGenerator = unsafe { EventGenerator::from_handle(event_send as usize) }; + // (unsafe {Arc::from_raw(event_send.cast()) }); + let subscription = gener.subscribe(); + // avoid consuming the generator + std::mem::forget(gener); + subscription +} + +pub unsafe fn activate_event_send_ptr(event_send: *mut EventGenerator2) { + let gener: EventGenerator = unsafe { EventGenerator::from_handle(event_send as usize) }; + gener.activate(); + // avoid consuming the generator + std::mem::forget(gener); +} + +// stolen from guest-rust/rt/src/lib.rs +pub struct Cleanup { + ptr: NonNull, + layout: Layout, +} + +// Usage of the returned pointer is always unsafe and must abide by these +// conventions, but this structure itself has no inherent reason to not be +// send/sync. +unsafe impl Send for Cleanup {} +unsafe impl Sync for Cleanup {} + +impl Cleanup { + pub fn new(layout: Layout) -> (*mut u8, Option) { + if layout.size() == 0 { + return (ptr::null_mut(), None); + } + let ptr = unsafe { alloc::alloc(layout) }; + let ptr = match NonNull::new(ptr) { + Some(ptr) => ptr, + None => alloc::handle_alloc_error(layout), + }; + (ptr.as_ptr(), Some(Cleanup { ptr, layout })) + } + pub fn forget(self) { + core::mem::forget(self); + } +} + +impl Drop for Cleanup { + fn drop(&mut self) { + unsafe { + alloc::dealloc(self.ptr.as_ptr(), self.layout); + } + } +} diff --git a/crates/symmetric_executor/rust-client/src/module.rs b/crates/symmetric_executor/rust-client/src/module.rs new file mode 100644 index 000000000..1ed0a9ff2 --- /dev/null +++ b/crates/symmetric_executor/rust-client/src/module.rs @@ -0,0 +1,1259 @@ +// Generated by `wit-bindgen` 0.41.0. DO NOT EDIT! +// Options used: +#[allow(dead_code, clippy::all)] +pub mod symmetric { + pub mod runtime { + /// This interface will only work with symmetric ABI (shared everything), + /// it can't be composed with the canonical ABI + /// Asynchronous executor functionality for symmetric ABI + #[allow(dead_code, async_fn_in_trait, unused_imports, clippy::all)] + pub mod symmetric_executor { + #[used] + #[doc(hidden)] + static __FORCE_SECTION_REF: fn() = + super::super::super::__link_custom_section_describing_imports; + + use super::super::super::_rt; + /// These pseudo-resources are just used to + /// pass pointers to register + /// Return value of an event callback + #[repr(u8)] + #[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)] + pub enum CallbackState { + /// Call the function again + Pending, + /// The function has completed, all results are written, data is freed, + /// calling the function again is not permitted as data became invalid! + Ready, + } + impl ::core::fmt::Debug for CallbackState { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + CallbackState::Pending => f.debug_tuple("CallbackState::Pending").finish(), + CallbackState::Ready => f.debug_tuple("CallbackState::Ready").finish(), + } + } + } + + impl CallbackState { + #[doc(hidden)] + pub unsafe fn _lift(val: u8) -> CallbackState { + if !cfg!(debug_assertions) { + return unsafe { ::core::mem::transmute(val) }; + } + + match val { + 0 => CallbackState::Pending, + 1 => CallbackState::Ready, + + _ => panic!("invalid enum discriminant"), + } + } + } + + /// This wraps a user provided function of type + /// `fn (callback-data) -> callback-state` + + #[derive(Debug)] + #[repr(transparent)] + pub struct CallbackFunction { + handle: _rt::Resource, + } + + impl CallbackFunction { + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + Self { + handle: unsafe { _rt::Resource::from_handle(handle) }, + } + } + + #[doc(hidden)] + pub fn take_handle(&self) -> usize { + _rt::Resource::take_handle(&self.handle) + } + + #[doc(hidden)] + pub fn handle(&self) -> usize { + _rt::Resource::handle(&self.handle) + } + } + + unsafe impl _rt::WasmResource for CallbackFunction { + #[inline] + unsafe fn drop(_handle: usize) { + { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[resource-drop]callback-function" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Dcallback_function( + _: usize, + ); + } + + unsafe { + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Dcallback_function(_handle) + }; + } + } + } + + /// This wraps opaque user data, freed by the callback when + /// it returns ready + + #[derive(Debug)] + #[repr(transparent)] + pub struct CallbackData { + handle: _rt::Resource, + } + + impl CallbackData { + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + Self { + handle: unsafe { _rt::Resource::from_handle(handle) }, + } + } + + #[doc(hidden)] + pub fn take_handle(&self) -> usize { + _rt::Resource::take_handle(&self.handle) + } + + #[doc(hidden)] + pub fn handle(&self) -> usize { + _rt::Resource::handle(&self.handle) + } + } + + unsafe impl _rt::WasmResource for CallbackData { + #[inline] + unsafe fn drop(_handle: usize) { + { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[resource-drop]callback-data" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Dcallback_data( + _: usize, + ); + } + + unsafe { + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Dcallback_data(_handle) + }; + } + } + } + + /// The receiving side of an event + + #[derive(Debug)] + #[repr(transparent)] + pub struct EventSubscription { + handle: _rt::Resource, + } + + impl EventSubscription { + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + Self { + handle: unsafe { _rt::Resource::from_handle(handle) }, + } + } + + #[doc(hidden)] + pub fn take_handle(&self) -> usize { + _rt::Resource::take_handle(&self.handle) + } + + #[doc(hidden)] + pub fn handle(&self) -> usize { + _rt::Resource::handle(&self.handle) + } + } + + unsafe impl _rt::WasmResource for EventSubscription { + #[inline] + unsafe fn drop(_handle: usize) { + { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[resource-drop]event-subscription" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Devent_subscription( + _: usize, + ); + } + + unsafe { + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Devent_subscription(_handle) + }; + } + } + } + + /// A user controlled event + + #[derive(Debug)] + #[repr(transparent)] + pub struct EventGenerator { + handle: _rt::Resource, + } + + impl EventGenerator { + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + Self { + handle: unsafe { _rt::Resource::from_handle(handle) }, + } + } + + #[doc(hidden)] + pub fn take_handle(&self) -> usize { + _rt::Resource::take_handle(&self.handle) + } + + #[doc(hidden)] + pub fn handle(&self) -> usize { + _rt::Resource::handle(&self.handle) + } + } + + unsafe impl _rt::WasmResource for EventGenerator { + #[inline] + unsafe fn drop(_handle: usize) { + { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[resource-drop]event-generator" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Devent_generator( + _: usize, + ); + } + + unsafe { + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Devent_generator(_handle) + }; + } + } + } + + /// Handle to cancel a callback registration + + #[derive(Debug)] + #[repr(transparent)] + pub struct CallbackRegistration { + handle: _rt::Resource, + } + + impl CallbackRegistration { + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + Self { + handle: unsafe { _rt::Resource::from_handle(handle) }, + } + } + + #[doc(hidden)] + pub fn take_handle(&self) -> usize { + _rt::Resource::take_handle(&self.handle) + } + + #[doc(hidden)] + pub fn handle(&self) -> usize { + _rt::Resource::handle(&self.handle) + } + } + + unsafe impl _rt::WasmResource for CallbackRegistration { + #[inline] + unsafe fn drop(_handle: usize) { + { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[resource-drop]callback-registration" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Dcallback_registration( + _: usize, + ); + } + + unsafe { + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Dcallback_registration(_handle) + }; + } + } + } + + /// Return value of an async call, lowest bit encoding + #[repr(u8)] + #[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)] + pub enum CallStatus { + /// For symmetric this means that processing has started, parameters should still remain valid until null, + /// params-read = non-null, results-written,done = null + Started, + /// For symmetric: Retry the call (temporarily out of memory) + NotStarted, + } + impl ::core::fmt::Debug for CallStatus { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + CallStatus::Started => f.debug_tuple("CallStatus::Started").finish(), + CallStatus::NotStarted => f.debug_tuple("CallStatus::NotStarted").finish(), + } + } + } + + impl CallStatus { + #[doc(hidden)] + pub unsafe fn _lift(val: u8) -> CallStatus { + if !cfg!(debug_assertions) { + return unsafe { ::core::mem::transmute(val) }; + } + + match val { + 0 => CallStatus::Started, + 1 => CallStatus::NotStarted, + + _ => panic!("invalid enum discriminant"), + } + } + } + + impl EventSubscription { + #[allow(unused_unsafe, clippy::all)] + /// Whether the event is active (used by poll implementation) + #[allow(async_fn_in_trait)] + pub fn ready(&self) -> bool { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[method]event-subscription.ready" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_subscriptionX2Eready( + _: *mut u8, + ) -> i32; + } + let ret = symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_subscriptionX2Eready((self).handle() as *mut u8); + _rt::bool_lift(ret as u8) + } + } + } + impl EventSubscription { + #[allow(unused_unsafe, clippy::all)] + /// Create a timeout event + #[allow(async_fn_in_trait)] + pub fn from_timeout(nanoseconds: u64) -> EventSubscription { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[static]event-subscription.from-timeout" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BstaticX5Devent_subscriptionX2Efrom_timeout( + _: i64, + ) -> *mut u8; + } + let ret = symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BstaticX5Devent_subscriptionX2Efrom_timeout(_rt::as_i64(&nanoseconds)); + EventSubscription::from_handle(ret as usize) + } + } + } + impl EventSubscription { + #[allow(unused_unsafe, clippy::all)] + /// Duplicate the subscription (e.g. for repeated callback registering, same cost as subscribe) + #[allow(async_fn_in_trait)] + pub fn dup(&self) -> EventSubscription { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[method]event-subscription.dup" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_subscriptionX2Edup( + _: *mut u8, + ) -> *mut u8; + } + let ret = symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_subscriptionX2Edup((self).handle() as *mut u8); + EventSubscription::from_handle(ret as usize) + } + } + } + impl EventSubscription { + #[allow(unused_unsafe, clippy::all)] + /// Reset subscription to be inactive, only next trigger will ready it + #[allow(async_fn_in_trait)] + pub fn reset(&self) -> () { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[method]event-subscription.reset" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_subscriptionX2Ereset( + _: *mut u8, + ); + } + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_subscriptionX2Ereset((self).handle() as *mut u8); + } + } + } + impl EventGenerator { + #[allow(unused_unsafe, clippy::all)] + #[allow(async_fn_in_trait)] + pub fn new() -> Self { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[constructor]event-generator" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BconstructorX5Devent_generator( + ) -> *mut u8; + } + let ret = symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BconstructorX5Devent_generator(); + EventGenerator::from_handle(ret as usize) + } + } + } + impl EventGenerator { + #[allow(unused_unsafe, clippy::all)] + /// Get the receiving side (to pass to other parts of the program) + #[allow(async_fn_in_trait)] + pub fn subscribe(&self) -> EventSubscription { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[method]event-generator.subscribe" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_generatorX2Esubscribe( + _: *mut u8, + ) -> *mut u8; + } + let ret = symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_generatorX2Esubscribe((self).handle() as *mut u8); + EventSubscription::from_handle(ret as usize) + } + } + } + impl EventGenerator { + #[allow(unused_unsafe, clippy::all)] + /// Trigger all subscribers + #[allow(async_fn_in_trait)] + pub fn activate(&self) -> () { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + #[cfg_attr(not(target_arch = "wasm32"), link(name = "symmetric_executor"))] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[method]event-generator.activate" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_generatorX2Eactivate( + _: *mut u8, + ); + } + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_generatorX2Eactivate((self).handle() as *mut u8); + } + } + } + impl CallbackRegistration { + #[allow(unused_unsafe, clippy::all)] + /// returns the data passed to the registration + #[allow(async_fn_in_trait)] + pub fn cancel(obj: CallbackRegistration) -> CallbackData { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[static]callback-registration.cancel" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BstaticX5Dcallback_registrationX2Ecancel( + _: *mut u8, + ) -> *mut u8; + } + let ret = symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BstaticX5Dcallback_registrationX2Ecancel((&obj).take_handle() as *mut u8); + CallbackData::from_handle(ret as usize) + } + } + } + #[allow(unused_unsafe, clippy::all)] + /// Wait until all registered events have completed + #[allow(async_fn_in_trait)] + pub fn run() -> () { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "run")] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00run(); + } + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00run(); + } + } + #[allow(unused_unsafe, clippy::all)] + /// Register a callback for an event + #[allow(async_fn_in_trait)] + pub fn register( + trigger: EventSubscription, + callback: CallbackFunction, + data: CallbackData, + ) -> CallbackRegistration { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "register")] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00register( + _: *mut u8, + _: *mut u8, + _: *mut u8, + ) -> *mut u8; + } + let ret = symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00register( + (&trigger).take_handle() as *mut u8, + (&callback).take_handle() as *mut u8, + (&data).take_handle() as *mut u8, + ); + CallbackRegistration::from_handle(ret as usize) + } + } + } + + /// language neutral stream implementation + #[allow(dead_code, async_fn_in_trait, unused_imports, clippy::all)] + pub mod symmetric_stream { + #[used] + #[doc(hidden)] + static __FORCE_SECTION_REF: fn() = + super::super::super::__link_custom_section_describing_imports; + + use super::super::super::_rt; + pub type EventSubscription = + super::super::super::symmetric::runtime::symmetric_executor::EventSubscription; + + #[derive(Debug)] + #[repr(transparent)] + pub struct Address { + handle: _rt::Resource
, + } + + impl Address { + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + Self { + handle: unsafe { _rt::Resource::from_handle(handle) }, + } + } + + #[doc(hidden)] + pub fn take_handle(&self) -> usize { + _rt::Resource::take_handle(&self.handle) + } + + #[doc(hidden)] + pub fn handle(&self) -> usize { + _rt::Resource::handle(&self.handle) + } + } + + unsafe impl _rt::WasmResource for Address { + #[inline] + unsafe fn drop(_handle: usize) { + { + #[link(wasm_import_module = "symmetric:runtime/symmetric-stream@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[resource-drop]address" + )] + fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5Bresource_dropX5Daddress( + _: usize, + ); + } + + unsafe { + symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5Bresource_dropX5Daddress(_handle) + }; + } + } + } + + /// special zero allocation/copy data type (caller provided buffer) + + #[derive(Debug)] + #[repr(transparent)] + pub struct Buffer { + handle: _rt::Resource, + } + + impl Buffer { + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + Self { + handle: unsafe { _rt::Resource::from_handle(handle) }, + } + } + + #[doc(hidden)] + pub fn take_handle(&self) -> usize { + _rt::Resource::take_handle(&self.handle) + } + + #[doc(hidden)] + pub fn handle(&self) -> usize { + _rt::Resource::handle(&self.handle) + } + } + + unsafe impl _rt::WasmResource for Buffer { + #[inline] + unsafe fn drop(_handle: usize) { + { + #[link(wasm_import_module = "symmetric:runtime/symmetric-stream@0.2.1")] + unsafe extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "[resource-drop]buffer")] + fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5Bresource_dropX5Dbuffer( + _: usize, + ); + } + + unsafe { + symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5Bresource_dropX5Dbuffer(_handle) + }; + } + } + } + + #[derive(Debug)] + #[repr(transparent)] + pub struct StreamObj { + handle: _rt::Resource, + } + + impl StreamObj { + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + Self { + handle: unsafe { _rt::Resource::from_handle(handle) }, + } + } + + #[doc(hidden)] + pub fn take_handle(&self) -> usize { + _rt::Resource::take_handle(&self.handle) + } + + #[doc(hidden)] + pub fn handle(&self) -> usize { + _rt::Resource::handle(&self.handle) + } + } + + unsafe impl _rt::WasmResource for StreamObj { + #[inline] + unsafe fn drop(_handle: usize) { + { + #[link(wasm_import_module = "symmetric:runtime/symmetric-stream@0.2.1")] + #[cfg_attr(not(target_arch = "wasm32"), link(name = "symmetric_stream"))] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[resource-drop]stream-obj" + )] + fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5Bresource_dropX5Dstream_obj( + _: usize, + ); + } + + unsafe { + symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5Bresource_dropX5Dstream_obj(_handle) + }; + } + } + } + + impl Buffer { + #[allow(unused_unsafe, clippy::all)] + #[allow(async_fn_in_trait)] + pub fn new(addr: Address, capacity: u64) -> Self { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-stream@0.2.1")] + unsafe extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "[constructor]buffer")] + fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BconstructorX5Dbuffer( + _: *mut u8, + _: i64, + ) -> *mut u8; + } + let ret = symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BconstructorX5Dbuffer((&addr).take_handle() as *mut u8, _rt::as_i64(&capacity)); + Buffer::from_handle(ret as usize) + } + } + } + impl Buffer { + #[allow(unused_unsafe, clippy::all)] + #[allow(async_fn_in_trait)] + pub fn get_address(&self) -> Address { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-stream@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[method]buffer.get-address" + )] + fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5DbufferX2Eget_address( + _: *mut u8, + ) -> *mut u8; + } + let ret = symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5DbufferX2Eget_address((self).handle() as *mut u8); + Address::from_handle(ret as usize) + } + } + } + impl Buffer { + #[allow(unused_unsafe, clippy::all)] + #[allow(async_fn_in_trait)] + pub fn get_size(&self) -> u64 { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-stream@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[method]buffer.get-size" + )] + fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5DbufferX2Eget_size( + _: *mut u8, + ) -> i64; + } + let ret = symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5DbufferX2Eget_size((self).handle() as *mut u8); + ret as u64 + } + } + } + impl Buffer { + #[allow(unused_unsafe, clippy::all)] + #[allow(async_fn_in_trait)] + pub fn set_size(&self, size: u64) -> () { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-stream@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[method]buffer.set-size" + )] + fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5DbufferX2Eset_size( + _: *mut u8, + _: i64, + ); + } + symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5DbufferX2Eset_size((self).handle() as *mut u8, _rt::as_i64(&size)); + } + } + } + impl Buffer { + #[allow(unused_unsafe, clippy::all)] + #[allow(async_fn_in_trait)] + pub fn capacity(&self) -> u64 { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-stream@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[method]buffer.capacity" + )] + fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5DbufferX2Ecapacity( + _: *mut u8, + ) -> i64; + } + let ret = symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5DbufferX2Ecapacity((self).handle() as *mut u8); + ret as u64 + } + } + } + impl StreamObj { + #[allow(unused_unsafe, clippy::all)] + #[allow(async_fn_in_trait)] + pub fn new() -> Self { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-stream@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[constructor]stream-obj" + )] + fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BconstructorX5Dstream_obj( + ) -> *mut u8; + } + let ret = symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BconstructorX5Dstream_obj(); + StreamObj::from_handle(ret as usize) + } + } + } + impl StreamObj { + #[allow(unused_unsafe, clippy::all)] + /// create a new instance e.g. for reading or tasks + #[allow(async_fn_in_trait)] + pub fn clone(&self) -> StreamObj { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-stream@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[method]stream-obj.clone" + )] + fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eclone( + _: *mut u8, + ) -> *mut u8; + } + let ret = symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eclone((self).handle() as *mut u8); + StreamObj::from_handle(ret as usize) + } + } + } + impl StreamObj { + #[allow(unused_unsafe, clippy::all)] + /// reading (in roughly chronological order) + #[allow(async_fn_in_trait)] + pub fn is_write_closed(&self) -> bool { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-stream@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[method]stream-obj.is-write-closed" + )] + fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eis_write_closed( + _: *mut u8, + ) -> i32; + } + let ret = symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eis_write_closed((self).handle() as *mut u8); + _rt::bool_lift(ret as u8) + } + } + } + impl StreamObj { + #[allow(unused_unsafe, clippy::all)] + #[allow(async_fn_in_trait)] + pub fn start_reading(&self, buffer: Buffer) -> () { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-stream@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[method]stream-obj.start-reading" + )] + fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Estart_reading( + _: *mut u8, + _: *mut u8, + ); + } + symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Estart_reading((self).handle() as *mut u8, (&buffer).take_handle() as *mut u8); + } + } + } + impl StreamObj { + #[allow(unused_unsafe, clippy::all)] + #[allow(async_fn_in_trait)] + pub fn write_ready_activate(&self) -> () { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-stream@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[method]stream-obj.write-ready-activate" + )] + fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Ewrite_ready_activate( + _: *mut u8, + ); + } + symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Ewrite_ready_activate((self).handle() as *mut u8); + } + } + } + impl StreamObj { + #[allow(unused_unsafe, clippy::all)] + #[allow(async_fn_in_trait)] + pub fn read_ready_subscribe(&self) -> EventSubscription { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-stream@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[method]stream-obj.read-ready-subscribe" + )] + fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eread_ready_subscribe( + _: *mut u8, + ) -> *mut u8; + } + let ret = symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eread_ready_subscribe((self).handle() as *mut u8); + super::super::super::symmetric::runtime::symmetric_executor::EventSubscription::from_handle(ret as usize) + } + } + } + impl StreamObj { + #[allow(unused_unsafe, clippy::all)] + /// none is EOF + #[allow(async_fn_in_trait)] + pub fn read_result(&self) -> Option { + unsafe { + #[cfg_attr(target_pointer_width = "64", repr(align(8)))] + #[cfg_attr(target_pointer_width = "32", repr(align(4)))] + struct RetArea( + [::core::mem::MaybeUninit; 2 * ::core::mem::size_of::<*const u8>()], + ); + let mut ret_area = RetArea( + [::core::mem::MaybeUninit::uninit(); + 2 * ::core::mem::size_of::<*const u8>()], + ); + let ptr0 = ret_area.0.as_mut_ptr().cast::(); + #[link(wasm_import_module = "symmetric:runtime/symmetric-stream@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[method]stream-obj.read-result" + )] + fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eread_result( + _: *mut u8, + _: *mut u8, + ); + } + symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eread_result((self).handle() as *mut u8, ptr0); + let l1 = i32::from(*ptr0.add(0).cast::()); + match l1 { + 0 => None, + 1 => { + let e = { + let l2 = *ptr0 + .add(::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>(); + + Buffer::from_handle(l2 as usize) + }; + Some(e) + } + _ => _rt::invalid_enum_discriminant(), + } + } + } + } + impl StreamObj { + #[allow(unused_unsafe, clippy::all)] + /// writing + #[allow(async_fn_in_trait)] + pub fn is_ready_to_write(&self) -> bool { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-stream@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[method]stream-obj.is-ready-to-write" + )] + fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eis_ready_to_write( + _: *mut u8, + ) -> i32; + } + let ret = symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eis_ready_to_write((self).handle() as *mut u8); + _rt::bool_lift(ret as u8) + } + } + } + impl StreamObj { + #[allow(unused_unsafe, clippy::all)] + #[allow(async_fn_in_trait)] + pub fn write_ready_subscribe(&self) -> EventSubscription { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-stream@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[method]stream-obj.write-ready-subscribe" + )] + fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Ewrite_ready_subscribe( + _: *mut u8, + ) -> *mut u8; + } + let ret = symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Ewrite_ready_subscribe((self).handle() as *mut u8); + super::super::super::symmetric::runtime::symmetric_executor::EventSubscription::from_handle(ret as usize) + } + } + } + impl StreamObj { + #[allow(unused_unsafe, clippy::all)] + #[allow(async_fn_in_trait)] + pub fn start_writing(&self) -> Buffer { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-stream@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[method]stream-obj.start-writing" + )] + fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Estart_writing( + _: *mut u8, + ) -> *mut u8; + } + let ret = symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Estart_writing((self).handle() as *mut u8); + Buffer::from_handle(ret as usize) + } + } + } + impl StreamObj { + #[allow(unused_unsafe, clippy::all)] + /// none is EOF + #[allow(async_fn_in_trait)] + pub fn finish_writing(&self, buffer: Option) -> () { + unsafe { + let (result0_0, result0_1) = match &buffer { + Some(e) => (1i32, (e).take_handle() as *mut u8), + None => (0i32, core::ptr::null_mut()), + }; + #[link(wasm_import_module = "symmetric:runtime/symmetric-stream@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[method]stream-obj.finish-writing" + )] + fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Efinish_writing( + _: *mut u8, + _: i32, + _: *mut u8, + ); + } + symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Efinish_writing((self).handle() as *mut u8, result0_0, result0_1); + } + } + } + impl StreamObj { + #[allow(unused_unsafe, clippy::all)] + #[allow(async_fn_in_trait)] + pub fn read_ready_activate(&self) -> () { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-stream@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[method]stream-obj.read-ready-activate" + )] + fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eread_ready_activate( + _: *mut u8, + ); + } + symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eread_ready_activate((self).handle() as *mut u8); + } + } + } + } + } +} +mod _rt { + #![allow(dead_code, clippy::all)] + + use core::fmt; + use core::marker; + use core::sync::atomic::{AtomicUsize, Ordering::Relaxed}; + + /// A type which represents a component model resource, either imported or + /// exported into this component. + /// + /// This is a low-level wrapper which handles the lifetime of the resource + /// (namely this has a destructor). The `T` provided defines the component model + /// intrinsics that this wrapper uses. + /// + /// One of the chief purposes of this type is to provide `Deref` implementations + /// to access the underlying data when it is owned. + /// + /// This type is primarily used in generated code for exported and imported + /// resources. + #[repr(transparent)] + pub struct Resource { + // NB: This would ideally be `usize` but it is not. The fact that this has + // interior mutability is not exposed in the API of this type except for the + // `take_handle` method which is supposed to in theory be private. + // + // This represents, almost all the time, a valid handle value. When it's + // invalid it's stored as `0`. + handle: AtomicUsize, + _marker: marker::PhantomData, + } + + /// A trait which all wasm resources implement, namely providing the ability to + /// drop a resource. + /// + /// This generally is implemented by generated code, not user-facing code. + #[allow(clippy::missing_safety_doc)] + pub unsafe trait WasmResource { + /// Invokes the `[resource-drop]...` intrinsic. + unsafe fn drop(handle: usize); + } + + impl Resource { + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + debug_assert!(handle != 0); + Self { + handle: AtomicUsize::new(handle), + _marker: marker::PhantomData, + } + } + + /// Takes ownership of the handle owned by `resource`. + /// + /// Note that this ideally would be `into_handle` taking `Resource` by + /// ownership. The code generator does not enable that in all situations, + /// unfortunately, so this is provided instead. + /// + /// Also note that `take_handle` is in theory only ever called on values + /// owned by a generated function. For example a generated function might + /// take `Resource` as an argument but then call `take_handle` on a + /// reference to that argument. In that sense the dynamic nature of + /// `take_handle` should only be exposed internally to generated code, not + /// to user code. + #[doc(hidden)] + pub fn take_handle(resource: &Resource) -> usize { + resource.handle.swap(0, Relaxed) + } + + #[doc(hidden)] + pub fn handle(resource: &Resource) -> usize { + resource.handle.load(Relaxed) + } + } + + impl fmt::Debug for Resource { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Resource") + .field("handle", &self.handle) + .finish() + } + } + + impl Drop for Resource { + fn drop(&mut self) { + unsafe { + match self.handle.load(Relaxed) { + // If this handle was "taken" then don't do anything in the + // destructor. + 0 => {} + + // ... but otherwise do actually destroy it with the imported + // component model intrinsic as defined through `T`. + other => T::drop(other), + } + } + } + } + pub unsafe fn bool_lift(val: u8) -> bool { + if cfg!(debug_assertions) { + match val { + 0 => false, + 1 => true, + _ => panic!("invalid bool discriminant"), + } + } else { + val != 0 + } + } + + pub fn as_i64(t: T) -> i64 { + t.as_i64() + } + + pub trait AsI64 { + fn as_i64(self) -> i64; + } + + impl<'a, T: Copy + AsI64> AsI64 for &'a T { + fn as_i64(self) -> i64 { + (*self).as_i64() + } + } + + impl AsI64 for i64 { + #[inline] + fn as_i64(self) -> i64 { + self as i64 + } + } + + impl AsI64 for u64 { + #[inline] + fn as_i64(self) -> i64 { + self as i64 + } + } + pub unsafe fn invalid_enum_discriminant() -> T { + if cfg!(debug_assertions) { + panic!("invalid enum discriminant") + } else { + unsafe { core::hint::unreachable_unchecked() } + } + } +} + +#[cfg(target_arch = "wasm32")] +#[unsafe(link_section = "component-type:wit-bindgen:0.41.0:symmetric:runtime@0.2.1:module:encoded world")] +#[doc(hidden)] +#[allow(clippy::octal_escapes)] +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 1796] = *b"\ +\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\x87\x0d\x01A\x02\x01\ +A\x05\x01B$\x01m\x02\x07pending\x05ready\x04\0\x0ecallback-state\x03\0\0\x04\0\x11\ +callback-function\x03\x01\x04\0\x0dcallback-data\x03\x01\x04\0\x12event-subscrip\ +tion\x03\x01\x04\0\x0fevent-generator\x03\x01\x04\0\x15callback-registration\x03\ +\x01\x01m\x02\x07started\x0bnot-started\x04\0\x0bcall-status\x03\0\x07\x01h\x04\x01\ +@\x01\x04self\x09\0\x7f\x04\0\x20[method]event-subscription.ready\x01\x0a\x01i\x04\ +\x01@\x01\x0bnanosecondsw\0\x0b\x04\0'[static]event-subscription.from-timeout\x01\ +\x0c\x01@\x01\x04self\x09\0\x0b\x04\0\x1e[method]event-subscription.dup\x01\x0d\x01\ +@\x01\x04self\x09\x01\0\x04\0\x20[method]event-subscription.reset\x01\x0e\x01i\x05\ +\x01@\0\0\x0f\x04\0\x1c[constructor]event-generator\x01\x10\x01h\x05\x01@\x01\x04\ +self\x11\0\x0b\x04\0![method]event-generator.subscribe\x01\x12\x01@\x01\x04self\x11\ +\x01\0\x04\0\x20[method]event-generator.activate\x01\x13\x01i\x06\x01i\x03\x01@\x01\ +\x03obj\x14\0\x15\x04\0$[static]callback-registration.cancel\x01\x16\x01@\0\x01\0\ +\x04\0\x03run\x01\x17\x01i\x02\x01@\x03\x07trigger\x0b\x08callback\x18\x04data\x15\ +\0\x14\x04\0\x08register\x01\x19\x03\0*symmetric:runtime/symmetric-executor@0.2.\ +1\x05\0\x02\x03\0\0\x12event-subscription\x01B*\x02\x03\x02\x01\x01\x04\0\x12eve\ +nt-subscription\x03\0\0\x04\0\x07address\x03\x01\x04\0\x06buffer\x03\x01\x04\0\x0a\ +stream-obj\x03\x01\x01i\x02\x01i\x03\x01@\x02\x04addr\x05\x08capacityw\0\x06\x04\ +\0\x13[constructor]buffer\x01\x07\x01h\x03\x01@\x01\x04self\x08\0\x05\x04\0\x1a[\ +method]buffer.get-address\x01\x09\x01@\x01\x04self\x08\0w\x04\0\x17[method]buffe\ +r.get-size\x01\x0a\x01@\x02\x04self\x08\x04sizew\x01\0\x04\0\x17[method]buffer.s\ +et-size\x01\x0b\x04\0\x17[method]buffer.capacity\x01\x0a\x01i\x04\x01@\0\0\x0c\x04\ +\0\x17[constructor]stream-obj\x01\x0d\x01h\x04\x01@\x01\x04self\x0e\0\x0c\x04\0\x18\ +[method]stream-obj.clone\x01\x0f\x01@\x01\x04self\x0e\0\x7f\x04\0\"[method]strea\ +m-obj.is-write-closed\x01\x10\x01@\x02\x04self\x0e\x06buffer\x06\x01\0\x04\0\x20\ +[method]stream-obj.start-reading\x01\x11\x01@\x01\x04self\x0e\x01\0\x04\0'[metho\ +d]stream-obj.write-ready-activate\x01\x12\x01i\x01\x01@\x01\x04self\x0e\0\x13\x04\ +\0'[method]stream-obj.read-ready-subscribe\x01\x14\x01k\x06\x01@\x01\x04self\x0e\ +\0\x15\x04\0\x1e[method]stream-obj.read-result\x01\x16\x04\0$[method]stream-obj.\ +is-ready-to-write\x01\x10\x04\0([method]stream-obj.write-ready-subscribe\x01\x14\ +\x01@\x01\x04self\x0e\0\x06\x04\0\x20[method]stream-obj.start-writing\x01\x17\x01\ +@\x02\x04self\x0e\x06buffer\x15\x01\0\x04\0![method]stream-obj.finish-writing\x01\ +\x18\x04\0&[method]stream-obj.read-ready-activate\x01\x12\x03\0(symmetric:runtim\ +e/symmetric-stream@0.2.1\x05\x02\x04\0\x1esymmetric:runtime/module@0.2.1\x04\0\x0b\ +\x0c\x01\0\x06module\x03\0\0\0G\x09producers\x01\x0cprocessed-by\x02\x0dwit-comp\ +onent\x070.228.0\x10wit-bindgen-rust\x060.41.0"; + +#[inline(never)] +#[doc(hidden)] +pub fn __link_custom_section_describing_imports() { + wit_bindgen::rt::maybe_link_cabi_realloc(); +} diff --git a/crates/symmetric_executor/src/executor.rs b/crates/symmetric_executor/src/executor.rs new file mode 100644 index 000000000..85314f2ff --- /dev/null +++ b/crates/symmetric_executor/src/executor.rs @@ -0,0 +1,1413 @@ +// Generated by `wit-bindgen` 0.41.0. DO NOT EDIT! +// Options used: +#[allow(dead_code, clippy::all)] +pub mod exports { + pub mod symmetric { + pub mod runtime { + /// This interface will only work with symmetric ABI (shared everything), + /// it can't be composed with the canonical ABI + /// Asynchronous executor functionality for symmetric ABI + #[allow(dead_code, async_fn_in_trait, unused_imports, clippy::all)] + pub mod symmetric_executor { + #[used] + #[doc(hidden)] + static __FORCE_SECTION_REF: fn() = + super::super::super::super::__link_custom_section_describing_imports; + + use super::super::super::super::_rt; + /// These pseudo-resources are just used to + /// pass pointers to register + /// Return value of an event callback + #[repr(u8)] + #[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)] + pub enum CallbackState { + /// Call the function again + Pending, + /// The function has completed, all results are written, data is freed, + /// calling the function again is not permitted as data became invalid! + Ready, + } + impl ::core::fmt::Debug for CallbackState { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + CallbackState::Pending => { + f.debug_tuple("CallbackState::Pending").finish() + } + CallbackState::Ready => f.debug_tuple("CallbackState::Ready").finish(), + } + } + } + + impl CallbackState { + #[doc(hidden)] + pub unsafe fn _lift(val: u8) -> CallbackState { + if !cfg!(debug_assertions) { + return unsafe { ::core::mem::transmute(val) }; + } + + match val { + 0 => CallbackState::Pending, + 1 => CallbackState::Ready, + + _ => panic!("invalid enum discriminant"), + } + } + } + + /// This wraps a user provided function of type + /// `fn (callback-data) -> callback-state` + + #[derive(Debug)] + #[repr(transparent)] + pub struct CallbackFunction { + handle: _rt::Resource, + } + + type _CallbackFunctionRep = Option; + + impl CallbackFunction { + /// Creates a new resource from the specified representation. + /// + /// This function will create a new resource handle by moving `val` onto + /// the heap and then passing that heap pointer to the component model to + /// create a handle. The owned handle is then returned as `CallbackFunction`. + pub fn new(val: T) -> Self { + Self::type_guard::(); + let val: _CallbackFunctionRep = Some(val); + let ptr: *mut _CallbackFunctionRep = + _rt::Box::into_raw(_rt::Box::new(val)); + unsafe { Self::from_handle(T::_resource_new(ptr.cast())) } + } + + /// Gets access to the underlying `T` which represents this resource. + pub fn get(&self) -> &T { + let ptr = unsafe { &*self.as_ptr::() }; + ptr.as_ref().unwrap() + } + + /// Gets mutable access to the underlying `T` which represents this + /// resource. + pub fn get_mut(&mut self) -> &mut T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.as_mut().unwrap() + } + + /// Consumes this resource and returns the underlying `T`. + pub fn into_inner(self) -> T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.take().unwrap() + } + + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + Self { + handle: unsafe { _rt::Resource::from_handle(handle) }, + } + } + + #[doc(hidden)] + pub fn take_handle(&self) -> usize { + _rt::Resource::take_handle(&self.handle) + } + + #[doc(hidden)] + pub fn handle(&self) -> usize { + _rt::Resource::handle(&self.handle) + } + + // It's theoretically possible to implement the `GuestCallbackFunction` trait twice + // so guard against using it with two different types here. + #[doc(hidden)] + fn type_guard() { + use core::any::TypeId; + static mut LAST_TYPE: Option = None; + unsafe { + assert!(!cfg!(target_feature = "atomics")); + let id = TypeId::of::(); + match LAST_TYPE { + Some(ty) => assert!( + ty == id, + "cannot use two types with this resource type" + ), + None => LAST_TYPE = Some(id), + } + } + } + + #[doc(hidden)] + pub unsafe fn dtor(handle: *mut u8) { + Self::type_guard::(); + let _ = + unsafe { _rt::Box::from_raw(handle as *mut _CallbackFunctionRep) }; + } + + fn as_ptr(&self) -> *mut _CallbackFunctionRep { + CallbackFunction::type_guard::(); + T::_resource_rep(self.handle()).cast() + } + } + + /// A borrowed version of [`CallbackFunction`] which represents a borrowed value + /// with the lifetime `'a`. + #[derive(Debug)] + #[repr(transparent)] + pub struct CallbackFunctionBorrow<'a> { + rep: *mut u8, + _marker: core::marker::PhantomData<&'a CallbackFunction>, + } + + impl<'a> CallbackFunctionBorrow<'a> { + #[doc(hidden)] + pub unsafe fn lift(rep: usize) -> Self { + Self { + rep: rep as *mut u8, + _marker: core::marker::PhantomData, + } + } + + /// Gets access to the underlying `T` in this resource. + pub fn get(&self) -> &T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.as_ref().unwrap() + } + + // NB: mutable access is not allowed due to the component model allowing + // multiple borrows of the same resource. + + fn as_ptr(&self) -> *mut _CallbackFunctionRep { + CallbackFunction::type_guard::(); + self.rep.cast() + } + } + + unsafe impl _rt::WasmResource for CallbackFunction { + #[inline] + unsafe fn drop(_handle: usize) { + { + #[link( + wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1" + )] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[resource-drop]callback-function" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Dcallback_function( + _: usize, + ); + } + + unsafe { + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Dcallback_function(_handle) + }; + } + } + } + + /// This wraps opaque user data, freed by the callback when + /// it returns ready + + #[derive(Debug)] + #[repr(transparent)] + pub struct CallbackData { + handle: _rt::Resource, + } + + type _CallbackDataRep = Option; + + impl CallbackData { + /// Creates a new resource from the specified representation. + /// + /// This function will create a new resource handle by moving `val` onto + /// the heap and then passing that heap pointer to the component model to + /// create a handle. The owned handle is then returned as `CallbackData`. + pub fn new(val: T) -> Self { + Self::type_guard::(); + let val: _CallbackDataRep = Some(val); + let ptr: *mut _CallbackDataRep = _rt::Box::into_raw(_rt::Box::new(val)); + unsafe { Self::from_handle(T::_resource_new(ptr.cast())) } + } + + /// Gets access to the underlying `T` which represents this resource. + pub fn get(&self) -> &T { + let ptr = unsafe { &*self.as_ptr::() }; + ptr.as_ref().unwrap() + } + + /// Gets mutable access to the underlying `T` which represents this + /// resource. + pub fn get_mut(&mut self) -> &mut T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.as_mut().unwrap() + } + + /// Consumes this resource and returns the underlying `T`. + pub fn into_inner(self) -> T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.take().unwrap() + } + + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + Self { + handle: unsafe { _rt::Resource::from_handle(handle) }, + } + } + + #[doc(hidden)] + pub fn take_handle(&self) -> usize { + _rt::Resource::take_handle(&self.handle) + } + + #[doc(hidden)] + pub fn handle(&self) -> usize { + _rt::Resource::handle(&self.handle) + } + + // It's theoretically possible to implement the `GuestCallbackData` trait twice + // so guard against using it with two different types here. + #[doc(hidden)] + fn type_guard() { + use core::any::TypeId; + static mut LAST_TYPE: Option = None; + unsafe { + assert!(!cfg!(target_feature = "atomics")); + let id = TypeId::of::(); + match LAST_TYPE { + Some(ty) => assert!( + ty == id, + "cannot use two types with this resource type" + ), + None => LAST_TYPE = Some(id), + } + } + } + + #[doc(hidden)] + pub unsafe fn dtor(handle: *mut u8) { + Self::type_guard::(); + let _ = unsafe { _rt::Box::from_raw(handle as *mut _CallbackDataRep) }; + } + + fn as_ptr(&self) -> *mut _CallbackDataRep { + CallbackData::type_guard::(); + T::_resource_rep(self.handle()).cast() + } + } + + /// A borrowed version of [`CallbackData`] which represents a borrowed value + /// with the lifetime `'a`. + #[derive(Debug)] + #[repr(transparent)] + pub struct CallbackDataBorrow<'a> { + rep: *mut u8, + _marker: core::marker::PhantomData<&'a CallbackData>, + } + + impl<'a> CallbackDataBorrow<'a> { + #[doc(hidden)] + pub unsafe fn lift(rep: usize) -> Self { + Self { + rep: rep as *mut u8, + _marker: core::marker::PhantomData, + } + } + + /// Gets access to the underlying `T` in this resource. + pub fn get(&self) -> &T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.as_ref().unwrap() + } + + // NB: mutable access is not allowed due to the component model allowing + // multiple borrows of the same resource. + + fn as_ptr(&self) -> *mut _CallbackDataRep { + CallbackData::type_guard::(); + self.rep.cast() + } + } + + unsafe impl _rt::WasmResource for CallbackData { + #[inline] + unsafe fn drop(_handle: usize) { + { + #[link( + wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1" + )] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[resource-drop]callback-data" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Dcallback_data( + _: usize, + ); + } + + unsafe { + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Dcallback_data(_handle) + }; + } + } + } + + /// The receiving side of an event + + #[derive(Debug)] + #[repr(transparent)] + pub struct EventSubscription { + handle: _rt::Resource, + } + + type _EventSubscriptionRep = Option; + + impl EventSubscription { + /// Creates a new resource from the specified representation. + /// + /// This function will create a new resource handle by moving `val` onto + /// the heap and then passing that heap pointer to the component model to + /// create a handle. The owned handle is then returned as `EventSubscription`. + pub fn new(val: T) -> Self { + Self::type_guard::(); + let val: _EventSubscriptionRep = Some(val); + let ptr: *mut _EventSubscriptionRep = + _rt::Box::into_raw(_rt::Box::new(val)); + unsafe { Self::from_handle(T::_resource_new(ptr.cast())) } + } + + /// Gets access to the underlying `T` which represents this resource. + pub fn get(&self) -> &T { + let ptr = unsafe { &*self.as_ptr::() }; + ptr.as_ref().unwrap() + } + + /// Gets mutable access to the underlying `T` which represents this + /// resource. + pub fn get_mut(&mut self) -> &mut T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.as_mut().unwrap() + } + + /// Consumes this resource and returns the underlying `T`. + pub fn into_inner(self) -> T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.take().unwrap() + } + + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + Self { + handle: unsafe { _rt::Resource::from_handle(handle) }, + } + } + + #[doc(hidden)] + pub fn take_handle(&self) -> usize { + _rt::Resource::take_handle(&self.handle) + } + + #[doc(hidden)] + pub fn handle(&self) -> usize { + _rt::Resource::handle(&self.handle) + } + + // It's theoretically possible to implement the `GuestEventSubscription` trait twice + // so guard against using it with two different types here. + #[doc(hidden)] + fn type_guard() { + use core::any::TypeId; + static mut LAST_TYPE: Option = None; + unsafe { + assert!(!cfg!(target_feature = "atomics")); + let id = TypeId::of::(); + match LAST_TYPE { + Some(ty) => assert!( + ty == id, + "cannot use two types with this resource type" + ), + None => LAST_TYPE = Some(id), + } + } + } + + #[doc(hidden)] + pub unsafe fn dtor(handle: *mut u8) { + Self::type_guard::(); + let _ = + unsafe { _rt::Box::from_raw(handle as *mut _EventSubscriptionRep) }; + } + + fn as_ptr(&self) -> *mut _EventSubscriptionRep { + EventSubscription::type_guard::(); + T::_resource_rep(self.handle()).cast() + } + } + + /// A borrowed version of [`EventSubscription`] which represents a borrowed value + /// with the lifetime `'a`. + #[derive(Debug)] + #[repr(transparent)] + pub struct EventSubscriptionBorrow<'a> { + rep: *mut u8, + _marker: core::marker::PhantomData<&'a EventSubscription>, + } + + impl<'a> EventSubscriptionBorrow<'a> { + #[doc(hidden)] + pub unsafe fn lift(rep: usize) -> Self { + Self { + rep: rep as *mut u8, + _marker: core::marker::PhantomData, + } + } + + /// Gets access to the underlying `T` in this resource. + pub fn get(&self) -> &T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.as_ref().unwrap() + } + + // NB: mutable access is not allowed due to the component model allowing + // multiple borrows of the same resource. + + fn as_ptr(&self) -> *mut _EventSubscriptionRep { + EventSubscription::type_guard::(); + self.rep.cast() + } + } + + unsafe impl _rt::WasmResource for EventSubscription { + #[inline] + unsafe fn drop(_handle: usize) { + { + #[link( + wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1" + )] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[resource-drop]event-subscription" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Devent_subscription( + _: usize, + ); + } + + unsafe { + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Devent_subscription(_handle) + }; + } + } + } + + /// A user controlled event + + #[derive(Debug)] + #[repr(transparent)] + pub struct EventGenerator { + handle: _rt::Resource, + } + + type _EventGeneratorRep = Option; + + impl EventGenerator { + /// Creates a new resource from the specified representation. + /// + /// This function will create a new resource handle by moving `val` onto + /// the heap and then passing that heap pointer to the component model to + /// create a handle. The owned handle is then returned as `EventGenerator`. + pub fn new(val: T) -> Self { + Self::type_guard::(); + let val: _EventGeneratorRep = Some(val); + let ptr: *mut _EventGeneratorRep = + _rt::Box::into_raw(_rt::Box::new(val)); + unsafe { Self::from_handle(T::_resource_new(ptr.cast())) } + } + + /// Gets access to the underlying `T` which represents this resource. + pub fn get(&self) -> &T { + let ptr = unsafe { &*self.as_ptr::() }; + ptr.as_ref().unwrap() + } + + /// Gets mutable access to the underlying `T` which represents this + /// resource. + pub fn get_mut(&mut self) -> &mut T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.as_mut().unwrap() + } + + /// Consumes this resource and returns the underlying `T`. + pub fn into_inner(self) -> T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.take().unwrap() + } + + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + Self { + handle: unsafe { _rt::Resource::from_handle(handle) }, + } + } + + #[doc(hidden)] + pub fn take_handle(&self) -> usize { + _rt::Resource::take_handle(&self.handle) + } + + #[doc(hidden)] + pub fn handle(&self) -> usize { + _rt::Resource::handle(&self.handle) + } + + // It's theoretically possible to implement the `GuestEventGenerator` trait twice + // so guard against using it with two different types here. + #[doc(hidden)] + fn type_guard() { + use core::any::TypeId; + static mut LAST_TYPE: Option = None; + unsafe { + assert!(!cfg!(target_feature = "atomics")); + let id = TypeId::of::(); + match LAST_TYPE { + Some(ty) => assert!( + ty == id, + "cannot use two types with this resource type" + ), + None => LAST_TYPE = Some(id), + } + } + } + + #[doc(hidden)] + pub unsafe fn dtor(handle: *mut u8) { + Self::type_guard::(); + let _ = unsafe { _rt::Box::from_raw(handle as *mut _EventGeneratorRep) }; + } + + fn as_ptr(&self) -> *mut _EventGeneratorRep { + EventGenerator::type_guard::(); + T::_resource_rep(self.handle()).cast() + } + } + + /// A borrowed version of [`EventGenerator`] which represents a borrowed value + /// with the lifetime `'a`. + #[derive(Debug)] + #[repr(transparent)] + pub struct EventGeneratorBorrow<'a> { + rep: *mut u8, + _marker: core::marker::PhantomData<&'a EventGenerator>, + } + + impl<'a> EventGeneratorBorrow<'a> { + #[doc(hidden)] + pub unsafe fn lift(rep: usize) -> Self { + Self { + rep: rep as *mut u8, + _marker: core::marker::PhantomData, + } + } + + /// Gets access to the underlying `T` in this resource. + pub fn get(&self) -> &T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.as_ref().unwrap() + } + + // NB: mutable access is not allowed due to the component model allowing + // multiple borrows of the same resource. + + fn as_ptr(&self) -> *mut _EventGeneratorRep { + EventGenerator::type_guard::(); + self.rep.cast() + } + } + + unsafe impl _rt::WasmResource for EventGenerator { + #[inline] + unsafe fn drop(_handle: usize) { + { + #[link( + wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1" + )] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[resource-drop]event-generator" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Devent_generator( + _: usize, + ); + } + + unsafe { + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Devent_generator(_handle) + }; + } + } + } + + /// Handle to cancel a callback registration + + #[derive(Debug)] + #[repr(transparent)] + pub struct CallbackRegistration { + handle: _rt::Resource, + } + + type _CallbackRegistrationRep = Option; + + impl CallbackRegistration { + /// Creates a new resource from the specified representation. + /// + /// This function will create a new resource handle by moving `val` onto + /// the heap and then passing that heap pointer to the component model to + /// create a handle. The owned handle is then returned as `CallbackRegistration`. + pub fn new(val: T) -> Self { + Self::type_guard::(); + let val: _CallbackRegistrationRep = Some(val); + let ptr: *mut _CallbackRegistrationRep = + _rt::Box::into_raw(_rt::Box::new(val)); + unsafe { Self::from_handle(T::_resource_new(ptr.cast())) } + } + + /// Gets access to the underlying `T` which represents this resource. + pub fn get(&self) -> &T { + let ptr = unsafe { &*self.as_ptr::() }; + ptr.as_ref().unwrap() + } + + /// Gets mutable access to the underlying `T` which represents this + /// resource. + pub fn get_mut(&mut self) -> &mut T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.as_mut().unwrap() + } + + /// Consumes this resource and returns the underlying `T`. + pub fn into_inner(self) -> T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.take().unwrap() + } + + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + Self { + handle: unsafe { _rt::Resource::from_handle(handle) }, + } + } + + #[doc(hidden)] + pub fn take_handle(&self) -> usize { + _rt::Resource::take_handle(&self.handle) + } + + #[doc(hidden)] + pub fn handle(&self) -> usize { + _rt::Resource::handle(&self.handle) + } + + // It's theoretically possible to implement the `GuestCallbackRegistration` trait twice + // so guard against using it with two different types here. + #[doc(hidden)] + fn type_guard() { + use core::any::TypeId; + static mut LAST_TYPE: Option = None; + unsafe { + assert!(!cfg!(target_feature = "atomics")); + let id = TypeId::of::(); + match LAST_TYPE { + Some(ty) => assert!( + ty == id, + "cannot use two types with this resource type" + ), + None => LAST_TYPE = Some(id), + } + } + } + + #[doc(hidden)] + pub unsafe fn dtor(handle: *mut u8) { + Self::type_guard::(); + let _ = unsafe { + _rt::Box::from_raw(handle as *mut _CallbackRegistrationRep) + }; + } + + fn as_ptr( + &self, + ) -> *mut _CallbackRegistrationRep { + CallbackRegistration::type_guard::(); + T::_resource_rep(self.handle()).cast() + } + } + + /// A borrowed version of [`CallbackRegistration`] which represents a borrowed value + /// with the lifetime `'a`. + #[derive(Debug)] + #[repr(transparent)] + pub struct CallbackRegistrationBorrow<'a> { + rep: *mut u8, + _marker: core::marker::PhantomData<&'a CallbackRegistration>, + } + + impl<'a> CallbackRegistrationBorrow<'a> { + #[doc(hidden)] + pub unsafe fn lift(rep: usize) -> Self { + Self { + rep: rep as *mut u8, + _marker: core::marker::PhantomData, + } + } + + /// Gets access to the underlying `T` in this resource. + pub fn get(&self) -> &T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.as_ref().unwrap() + } + + // NB: mutable access is not allowed due to the component model allowing + // multiple borrows of the same resource. + + fn as_ptr(&self) -> *mut _CallbackRegistrationRep { + CallbackRegistration::type_guard::(); + self.rep.cast() + } + } + + unsafe impl _rt::WasmResource for CallbackRegistration { + #[inline] + unsafe fn drop(_handle: usize) { + { + #[link( + wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1" + )] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[resource-drop]callback-registration" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Dcallback_registration( + _: usize, + ); + } + + unsafe { + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Dcallback_registration(_handle) + }; + } + } + } + + /// Return value of an async call, lowest bit encoding + #[repr(u8)] + #[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)] + pub enum CallStatus { + /// For symmetric this means that processing has started, parameters should still remain valid until null, + /// params-read = non-null, results-written,done = null + Started, + /// For symmetric: Retry the call (temporarily out of memory) + NotStarted, + } + impl ::core::fmt::Debug for CallStatus { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + CallStatus::Started => f.debug_tuple("CallStatus::Started").finish(), + CallStatus::NotStarted => { + f.debug_tuple("CallStatus::NotStarted").finish() + } + } + } + } + + impl CallStatus { + #[doc(hidden)] + pub unsafe fn _lift(val: u8) -> CallStatus { + if !cfg!(debug_assertions) { + return unsafe { ::core::mem::transmute(val) }; + } + + match val { + 0 => CallStatus::Started, + 1 => CallStatus::NotStarted, + + _ => panic!("invalid enum discriminant"), + } + } + } + + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_method_event_subscription_ready_cabi< + T: GuestEventSubscription, + >( + arg0: *mut u8, + ) -> i32 { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let result0 = + { T::ready(EventSubscriptionBorrow::lift(arg0 as usize).get()) }; + match result0 { + true => 1, + false => 0, + } + } + } + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_static_event_subscription_from_timeout_cabi< + T: GuestEventSubscription, + >( + arg0: i64, + ) -> *mut u8 { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let result0 = { T::from_timeout(arg0 as u64) }; + (result0).take_handle() as *mut u8 + } + } + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_method_event_subscription_dup_cabi< + T: GuestEventSubscription, + >( + arg0: *mut u8, + ) -> *mut u8 { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let result0 = + { T::dup(EventSubscriptionBorrow::lift(arg0 as usize).get()) }; + (result0).take_handle() as *mut u8 + } + } + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_method_event_subscription_reset_cabi< + T: GuestEventSubscription, + >( + arg0: *mut u8, + ) { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + { + T::reset(EventSubscriptionBorrow::lift(arg0 as usize).get()) + }; + } + } + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_constructor_event_generator_cabi( + ) -> *mut u8 { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let result0 = { EventGenerator::new(T::new()) }; + (result0).take_handle() as *mut u8 + } + } + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_method_event_generator_subscribe_cabi< + T: GuestEventGenerator, + >( + arg0: *mut u8, + ) -> *mut u8 { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let result0 = + { T::subscribe(EventGeneratorBorrow::lift(arg0 as usize).get()) }; + (result0).take_handle() as *mut u8 + } + } + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_method_event_generator_activate_cabi< + T: GuestEventGenerator, + >( + arg0: *mut u8, + ) { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + { + T::activate(EventGeneratorBorrow::lift(arg0 as usize).get()) + }; + } + } + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_static_callback_registration_cancel_cabi< + T: GuestCallbackRegistration, + >( + arg0: *mut u8, + ) -> *mut u8 { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let result0 = + { T::cancel(CallbackRegistration::from_handle(arg0 as usize)) }; + (result0).take_handle() as *mut u8 + } + } + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_run_cabi() { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + { + T::run() + }; + } + } + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_register_cabi( + arg0: *mut u8, + arg1: *mut u8, + arg2: *mut u8, + ) -> *mut u8 { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let result0 = { + T::register( + EventSubscription::from_handle(arg0 as usize), + CallbackFunction::from_handle(arg1 as usize), + CallbackData::from_handle(arg2 as usize), + ) + }; + (result0).take_handle() as *mut u8 + } + } + pub trait Guest { + type CallbackFunction: GuestCallbackFunction; + type CallbackData: GuestCallbackData; + type EventSubscription: GuestEventSubscription; + type EventGenerator: GuestEventGenerator; + type CallbackRegistration: GuestCallbackRegistration; + /// Wait until all registered events have completed + #[allow(async_fn_in_trait)] + fn run() -> (); + /// Register a callback for an event + #[allow(async_fn_in_trait)] + fn register( + trigger: EventSubscription, + callback: CallbackFunction, + data: CallbackData, + ) -> CallbackRegistration; + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_drop_callbackFunction_cabi( + arg0: usize, + ) { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + CallbackFunction::dtor::(arg0 as *mut u8); + } + pub trait GuestCallbackFunction: 'static { + #[doc(hidden)] + unsafe fn _resource_new(val: *mut u8) -> usize + where + Self: Sized, + { + val as usize + } + + #[doc(hidden)] + fn _resource_rep(handle: usize) -> *mut u8 + where + Self: Sized, + { + handle as *mut u8 + } + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_drop_callbackData_cabi(arg0: usize) { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + CallbackData::dtor::(arg0 as *mut u8); + } + pub trait GuestCallbackData: 'static { + #[doc(hidden)] + unsafe fn _resource_new(val: *mut u8) -> usize + where + Self: Sized, + { + val as usize + } + + #[doc(hidden)] + fn _resource_rep(handle: usize) -> *mut u8 + where + Self: Sized, + { + handle as *mut u8 + } + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_drop_eventSubscription_cabi( + arg0: usize, + ) { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + EventSubscription::dtor::(arg0 as *mut u8); + } + pub trait GuestEventSubscription: 'static { + #[doc(hidden)] + unsafe fn _resource_new(val: *mut u8) -> usize + where + Self: Sized, + { + val as usize + } + + #[doc(hidden)] + fn _resource_rep(handle: usize) -> *mut u8 + where + Self: Sized, + { + handle as *mut u8 + } + + /// Whether the event is active (used by poll implementation) + #[allow(async_fn_in_trait)] + fn ready(&self) -> bool; + /// Create a timeout event + #[allow(async_fn_in_trait)] + fn from_timeout(nanoseconds: u64) -> EventSubscription; + /// Duplicate the subscription (e.g. for repeated callback registering, same cost as subscribe) + #[allow(async_fn_in_trait)] + fn dup(&self) -> EventSubscription; + /// Reset subscription to be inactive, only next trigger will ready it + #[allow(async_fn_in_trait)] + fn reset(&self) -> (); + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_drop_eventGenerator_cabi( + arg0: usize, + ) { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + EventGenerator::dtor::(arg0 as *mut u8); + } + pub trait GuestEventGenerator: 'static { + #[doc(hidden)] + unsafe fn _resource_new(val: *mut u8) -> usize + where + Self: Sized, + { + val as usize + } + + #[doc(hidden)] + fn _resource_rep(handle: usize) -> *mut u8 + where + Self: Sized, + { + handle as *mut u8 + } + + #[allow(async_fn_in_trait)] + fn new() -> Self; + /// Get the receiving side (to pass to other parts of the program) + #[allow(async_fn_in_trait)] + fn subscribe(&self) -> EventSubscription; + /// Trigger all subscribers + #[allow(async_fn_in_trait)] + fn activate(&self) -> (); + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_drop_callbackRegistration_cabi< + T: GuestCallbackRegistration, + >( + arg0: usize, + ) { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + CallbackRegistration::dtor::(arg0 as *mut u8); + } + pub trait GuestCallbackRegistration: 'static { + #[doc(hidden)] + unsafe fn _resource_new(val: *mut u8) -> usize + where + Self: Sized, + { + val as usize + } + + #[doc(hidden)] + fn _resource_rep(handle: usize) -> *mut u8 + where + Self: Sized, + { + handle as *mut u8 + } + + /// returns the data passed to the registration + #[allow(async_fn_in_trait)] + fn cancel(obj: CallbackRegistration) -> CallbackData; + } + #[doc(hidden)] + + macro_rules! __export_symmetric_runtime_symmetric_executor_0_2_1_cabi{ + ($ty:ident with_types_in $($path_to_types:tt)*) => (const _: () = { + + #[cfg_attr(target_arch = "wasm32", export_name = "[method]event-subscription.ready")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_subscriptionX2Eready(arg0: *mut u8,) -> i32 { + unsafe { $($path_to_types)*::_export_method_event_subscription_ready_cabi::<<$ty as $($path_to_types)*::Guest>::EventSubscription>(arg0) } + } + #[cfg_attr(target_arch = "wasm32", export_name = "[static]event-subscription.from-timeout")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BstaticX5Devent_subscriptionX2Efrom_timeout(arg0: i64,) -> *mut u8 { + unsafe { $($path_to_types)*::_export_static_event_subscription_from_timeout_cabi::<<$ty as $($path_to_types)*::Guest>::EventSubscription>(arg0) } + } + #[cfg_attr(target_arch = "wasm32", export_name = "[method]event-subscription.dup")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_subscriptionX2Edup(arg0: *mut u8,) -> *mut u8 { + unsafe { $($path_to_types)*::_export_method_event_subscription_dup_cabi::<<$ty as $($path_to_types)*::Guest>::EventSubscription>(arg0) } + } + #[cfg_attr(target_arch = "wasm32", export_name = "[method]event-subscription.reset")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_subscriptionX2Ereset(arg0: *mut u8,) { + unsafe { $($path_to_types)*::_export_method_event_subscription_reset_cabi::<<$ty as $($path_to_types)*::Guest>::EventSubscription>(arg0) } + } + #[cfg_attr(target_arch = "wasm32", export_name = "[constructor]event-generator")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BconstructorX5Devent_generator() -> *mut u8 { + unsafe { $($path_to_types)*::_export_constructor_event_generator_cabi::<<$ty as $($path_to_types)*::Guest>::EventGenerator>() } + } + #[cfg_attr(target_arch = "wasm32", export_name = "[method]event-generator.subscribe")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_generatorX2Esubscribe(arg0: *mut u8,) -> *mut u8 { + unsafe { $($path_to_types)*::_export_method_event_generator_subscribe_cabi::<<$ty as $($path_to_types)*::Guest>::EventGenerator>(arg0) } + } + #[cfg_attr(target_arch = "wasm32", export_name = "[method]event-generator.activate")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_generatorX2Eactivate(arg0: *mut u8,) { + unsafe { $($path_to_types)*::_export_method_event_generator_activate_cabi::<<$ty as $($path_to_types)*::Guest>::EventGenerator>(arg0) } + } + #[cfg_attr(target_arch = "wasm32", export_name = "[static]callback-registration.cancel")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BstaticX5Dcallback_registrationX2Ecancel(arg0: *mut u8,) -> *mut u8 { + unsafe { $($path_to_types)*::_export_static_callback_registration_cancel_cabi::<<$ty as $($path_to_types)*::Guest>::CallbackRegistration>(arg0) } + } + #[cfg_attr(target_arch = "wasm32", export_name = "run")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00run() { + unsafe { $($path_to_types)*::_export_run_cabi::<$ty>() } + } + #[cfg_attr(target_arch = "wasm32", export_name = "register")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00register(arg0: *mut u8,arg1: *mut u8,arg2: *mut u8,) -> *mut u8 { + unsafe { $($path_to_types)*::_export_register_cabi::<$ty>(arg0, arg1, arg2) } + } + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Dcallback_function(arg0: usize) { + $($path_to_types)*::_export_drop_callbackFunction_cabi::<<$ty as $($path_to_types)*::Guest>::CallbackFunction>(arg0) + } + + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Dcallback_data(arg0: usize) { + $($path_to_types)*::_export_drop_callbackData_cabi::<<$ty as $($path_to_types)*::Guest>::CallbackData>(arg0) + } + + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Devent_subscription(arg0: usize) { + $($path_to_types)*::_export_drop_eventSubscription_cabi::<<$ty as $($path_to_types)*::Guest>::EventSubscription>(arg0) + } + + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Devent_generator(arg0: usize) { + $($path_to_types)*::_export_drop_eventGenerator_cabi::<<$ty as $($path_to_types)*::Guest>::EventGenerator>(arg0) + } + + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Dcallback_registration(arg0: usize) { + $($path_to_types)*::_export_drop_callbackRegistration_cabi::<<$ty as $($path_to_types)*::Guest>::CallbackRegistration>(arg0) + } + + };); +} + #[doc(hidden)] + pub(crate) use __export_symmetric_runtime_symmetric_executor_0_2_1_cabi; + } + } + } +} +mod _rt { + #![allow(dead_code, clippy::all)] + + use core::fmt; + use core::marker; + use core::sync::atomic::{AtomicUsize, Ordering::Relaxed}; + + /// A type which represents a component model resource, either imported or + /// exported into this component. + /// + /// This is a low-level wrapper which handles the lifetime of the resource + /// (namely this has a destructor). The `T` provided defines the component model + /// intrinsics that this wrapper uses. + /// + /// One of the chief purposes of this type is to provide `Deref` implementations + /// to access the underlying data when it is owned. + /// + /// This type is primarily used in generated code for exported and imported + /// resources. + #[repr(transparent)] + pub struct Resource { + // NB: This would ideally be `usize` but it is not. The fact that this has + // interior mutability is not exposed in the API of this type except for the + // `take_handle` method which is supposed to in theory be private. + // + // This represents, almost all the time, a valid handle value. When it's + // invalid it's stored as `0`. + handle: AtomicUsize, + _marker: marker::PhantomData, + } + + /// A trait which all wasm resources implement, namely providing the ability to + /// drop a resource. + /// + /// This generally is implemented by generated code, not user-facing code. + #[allow(clippy::missing_safety_doc)] + pub unsafe trait WasmResource { + /// Invokes the `[resource-drop]...` intrinsic. + unsafe fn drop(handle: usize); + } + + impl Resource { + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + debug_assert!(handle != 0); + Self { + handle: AtomicUsize::new(handle), + _marker: marker::PhantomData, + } + } + + /// Takes ownership of the handle owned by `resource`. + /// + /// Note that this ideally would be `into_handle` taking `Resource` by + /// ownership. The code generator does not enable that in all situations, + /// unfortunately, so this is provided instead. + /// + /// Also note that `take_handle` is in theory only ever called on values + /// owned by a generated function. For example a generated function might + /// take `Resource` as an argument but then call `take_handle` on a + /// reference to that argument. In that sense the dynamic nature of + /// `take_handle` should only be exposed internally to generated code, not + /// to user code. + #[doc(hidden)] + pub fn take_handle(resource: &Resource) -> usize { + resource.handle.swap(0, Relaxed) + } + + #[doc(hidden)] + pub fn handle(resource: &Resource) -> usize { + resource.handle.load(Relaxed) + } + } + + impl fmt::Debug for Resource { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Resource") + .field("handle", &self.handle) + .finish() + } + } + + impl Drop for Resource { + fn drop(&mut self) { + unsafe { + match self.handle.load(Relaxed) { + // If this handle was "taken" then don't do anything in the + // destructor. + 0 => {} + + // ... but otherwise do actually destroy it with the imported + // component model intrinsic as defined through `T`. + other => T::drop(other), + } + } + } + } + pub use alloc_crate::boxed::Box; + + #[cfg(target_arch = "wasm32")] + pub fn run_ctors_once() { + wit_bindgen::rt::run_ctors_once(); + } + extern crate alloc as alloc_crate; +} + +/// Generates `#[unsafe(no_mangle)]` functions to export the specified type as +/// the root implementation of all generated traits. +/// +/// For more information see the documentation of `wit_bindgen::generate!`. +/// +/// ```rust +/// # macro_rules! export{ ($($t:tt)*) => (); } +/// # trait Guest {} +/// struct MyType; +/// +/// impl Guest for MyType { +/// // ... +/// } +/// +/// export!(MyType); +/// ``` +#[allow(unused_macros)] +#[doc(hidden)] + +macro_rules! __export_executor_impl { + ($ty:ident) => (self::export!($ty with_types_in self);); + ($ty:ident with_types_in $($path_to_types_root:tt)*) => ( + $($path_to_types_root)*::exports::symmetric::runtime::symmetric_executor::__export_symmetric_runtime_symmetric_executor_0_2_1_cabi!($ty with_types_in $($path_to_types_root)*::exports::symmetric::runtime::symmetric_executor); + ) +} +#[doc(inline)] +pub(crate) use __export_executor_impl as export; + +#[cfg(target_arch = "wasm32")] +#[unsafe(link_section = "component-type:wit-bindgen:0.41.0:symmetric:runtime@0.2.1:executor:encoded world")] +#[doc(hidden)] +#[allow(clippy::octal_escapes)] +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 873] = *b"\ +\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\xea\x05\x01A\x02\x01\ +A\x02\x01B$\x01m\x02\x07pending\x05ready\x04\0\x0ecallback-state\x03\0\0\x04\0\x11\ +callback-function\x03\x01\x04\0\x0dcallback-data\x03\x01\x04\0\x12event-subscrip\ +tion\x03\x01\x04\0\x0fevent-generator\x03\x01\x04\0\x15callback-registration\x03\ +\x01\x01m\x02\x07started\x0bnot-started\x04\0\x0bcall-status\x03\0\x07\x01h\x04\x01\ +@\x01\x04self\x09\0\x7f\x04\0\x20[method]event-subscription.ready\x01\x0a\x01i\x04\ +\x01@\x01\x0bnanosecondsw\0\x0b\x04\0'[static]event-subscription.from-timeout\x01\ +\x0c\x01@\x01\x04self\x09\0\x0b\x04\0\x1e[method]event-subscription.dup\x01\x0d\x01\ +@\x01\x04self\x09\x01\0\x04\0\x20[method]event-subscription.reset\x01\x0e\x01i\x05\ +\x01@\0\0\x0f\x04\0\x1c[constructor]event-generator\x01\x10\x01h\x05\x01@\x01\x04\ +self\x11\0\x0b\x04\0![method]event-generator.subscribe\x01\x12\x01@\x01\x04self\x11\ +\x01\0\x04\0\x20[method]event-generator.activate\x01\x13\x01i\x06\x01i\x03\x01@\x01\ +\x03obj\x14\0\x15\x04\0$[static]callback-registration.cancel\x01\x16\x01@\0\x01\0\ +\x04\0\x03run\x01\x17\x01i\x02\x01@\x03\x07trigger\x0b\x08callback\x18\x04data\x15\ +\0\x14\x04\0\x08register\x01\x19\x04\0*symmetric:runtime/symmetric-executor@0.2.\ +1\x05\0\x04\0\x20symmetric:runtime/executor@0.2.1\x04\0\x0b\x0e\x01\0\x08executo\ +r\x03\0\0\0G\x09producers\x01\x0cprocessed-by\x02\x0dwit-component\x070.228.0\x10\ +wit-bindgen-rust\x060.41.0"; + +#[inline(never)] +#[doc(hidden)] +pub fn __link_custom_section_describing_imports() { + wit_bindgen::rt::maybe_link_cabi_realloc(); +} diff --git a/crates/symmetric_executor/src/lib.rs b/crates/symmetric_executor/src/lib.rs new file mode 100644 index 000000000..1d35b4b72 --- /dev/null +++ b/crates/symmetric_executor/src/lib.rs @@ -0,0 +1,590 @@ +use std::{ + mem::transmute, + sync::{ + atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering}, + Arc, Mutex, + }, + time::{Duration, SystemTime}, +}; + +use executor::exports::symmetric::runtime::symmetric_executor::{ + self, CallbackState, GuestCallbackRegistration, +}; + +const DEBUGGING: bool = cfg!(feature = "trace"); +const INVALID_FD: EventFd = -1; + +mod executor; + +struct Guest; + +executor::export!(Guest with_types_in executor); + +struct Ignore; +struct OpaqueData; +impl symmetric_executor::GuestCallbackFunction for Ignore {} +impl symmetric_executor::GuestCallbackData for OpaqueData {} + +// Hide the specifics of eventfd +mod event_fd { + pub type EventFd = core::ffi::c_int; + + pub fn activate(fd: EventFd) { + let file_signal: u64 = 1; + if super::DEBUGGING { + println!("activate(fd {fd})"); + } + + let result = unsafe { + libc::write( + fd, + core::ptr::from_ref(&file_signal).cast(), + core::mem::size_of_val(&file_signal), + ) + }; + if result >= 0 { + assert_eq!( + result, + core::mem::size_of_val(&file_signal).try_into().unwrap() + ); + } + } + pub fn consume(fd: EventFd) { + let mut dummy: u64 = 0; + + let readresult = unsafe { + libc::read( + fd, + core::ptr::from_mut(&mut dummy).cast(), + core::mem::size_of_val(&dummy), + ) + }; + assert!( + readresult <= 0 + || readresult == isize::try_from(core::mem::size_of_val(&dummy)).unwrap() + ); + } +} + +use event_fd::EventFd; + +struct WaitSet { + wait: libc::timeval, + // non null if timeval is finite (timeout) + tvptr: *mut libc::timeval, + maxfd: EventFd, + rfds: core::mem::MaybeUninit, +} + +struct WaitSetIterator<'a> { + ws: &'a WaitSet, + fd: EventFd, +} + +impl<'a> Iterator for WaitSetIterator<'a> { + type Item = EventFd; + + fn next(&mut self) -> Option { + let rfd_ptr = self.ws.rfds.as_ptr(); + while self.fd < self.ws.maxfd { + let fd = self.fd; + self.fd += 1; + if unsafe { libc::FD_ISSET(fd, rfd_ptr) } { + return Some(fd); + } + } + None + } +} + +impl WaitSet { + fn new(change_event: Option) -> Self { + let wait = libc::timeval { + tv_sec: i64::MAX, + tv_usec: 999999, + }; + let tvptr = core::ptr::null_mut(); + let maxfd = change_event.map_or(0, |fd| fd + 1); + let mut rfds = core::mem::MaybeUninit::::uninit(); + let rfd_ptr = rfds.as_mut_ptr(); + unsafe { libc::FD_ZERO(rfd_ptr) }; + if let Some(fd) = change_event { + unsafe { + libc::FD_SET(fd, rfd_ptr); + } + } + Self { + wait, + tvptr, + maxfd, + rfds, + } + } + + fn register(&mut self, fd: EventFd) { + let rfd_ptr = self.rfds.as_mut_ptr(); + unsafe { libc::FD_SET(fd, rfd_ptr) }; + if fd >= self.maxfd { + self.maxfd = fd + 1; + } + } + + fn timeout(&mut self, diff: Duration) { + let secs = diff.as_secs() as i64; + let usecs = diff.subsec_micros() as i64; + if secs < self.wait.tv_sec || (secs == self.wait.tv_sec && usecs < self.wait.tv_usec) { + self.wait.tv_sec = secs; + self.wait.tv_usec = usecs; + } + self.tvptr = core::ptr::from_mut(&mut self.wait); + } + + fn debug(&self) { + let rfd_ptr = self.rfds.as_ptr(); + if self.tvptr.is_null() { + println!("select({}, {:x}, null)", self.maxfd, unsafe { + *rfd_ptr.cast::() + },); + } else { + println!( + "select({}, {:x}, {}.{})", + self.maxfd, + unsafe { *rfd_ptr.cast::() }, + self.wait.tv_sec, + self.wait.tv_usec + ); + } + } + + // see select for the return value + fn wait(&mut self) -> i32 { + let rfd_ptr = self.rfds.as_mut_ptr(); + unsafe { + libc::select( + self.maxfd, + rfd_ptr, + std::ptr::null_mut(), + std::ptr::null_mut(), + self.tvptr, + ) + } + } + + fn iter_active(&self) -> WaitSetIterator<'_> { + WaitSetIterator { ws: self, fd: 0 } + } +} + +#[allow(dead_code)] +struct CallbackRegistrationInternal(usize); + +impl symmetric_executor::GuestEventSubscription for EventSubscriptionInternal { + fn ready(&self) -> bool { + self.inner.ready() + } + + fn from_timeout(nanoseconds: u64) -> symmetric_executor::EventSubscription { + let when = SystemTime::now() + Duration::from_nanos(nanoseconds); + symmetric_executor::EventSubscription::new(EventSubscriptionInternal { + inner: EventType::SystemTime(when), + }) + } + + fn dup(&self) -> symmetric_executor::EventSubscription { + let res = symmetric_executor::EventSubscription::new(self.dup()); + // to avoid endless recursion de-activate the original + self.reset(); + res + } + + fn reset(&self) { + match &self.inner { + EventType::Triggered { + last_counter, + event, + } => { + last_counter.store(event.lock().unwrap().counter, Ordering::Relaxed); + } + EventType::SystemTime(_system_time) => (), + } + } +} + +impl symmetric_executor::GuestEventGenerator for EventGenerator { + fn new() -> Self { + Self(Arc::new(Mutex::new(EventInner { + counter: 0, + waiting: Default::default(), + }))) + } + + fn subscribe(&self) -> symmetric_executor::EventSubscription { + if DEBUGGING { + println!("subscribe({:x})", Arc::as_ptr(&self.0) as usize); + } + symmetric_executor::EventSubscription::new(EventSubscriptionInternal { + inner: EventType::Triggered { + last_counter: AtomicU32::new(0), + event: Arc::clone(&self.0), + }, + }) + } + + fn activate(&self) { + if let Ok(mut event) = self.0.lock() { + event.counter += 1; + if DEBUGGING { + println!( + "activate({:x}) counter={}", + Arc::as_ptr(&self.0) as usize, + event.counter + ); + } + event.waiting.iter().for_each(|fd| { + event_fd::activate(*fd); + }); + } else if DEBUGGING { + println!("activate failure"); + } + } +} + +impl GuestCallbackRegistration for CallbackRegistrationInternal { + fn cancel(_obj: symmetric_executor::CallbackRegistration) -> symmetric_executor::CallbackData { + todo!() + } +} + +struct Executor { + active_tasks: Vec, + change_event: Option, +} + +impl Executor { + fn change_event(&mut self) -> EventFd { + *self.change_event.get_or_insert_with(|| { + let fd = unsafe { libc::eventfd(0, libc::EFD_NONBLOCK) }; + if DEBUGGING { + println!("change event fd={}", fd); + } + fd + }) + } + + /// run the executor until it would block, returns number of handled events + fn tick(ex: &mut std::sync::MutexGuard<'_, Self>, ws: &mut WaitSet) -> (usize, usize) { + let mut count_events = 0; + let mut count_waiting = 0; + let now = SystemTime::now(); + let old_busy = EXECUTOR_BUSY.swap(true, Ordering::Acquire); + assert!(!old_busy); + ex.active_tasks.iter_mut().for_each(|task| { + if task.inner.ready() { + if DEBUGGING { + println!( + "task ready {:x} {:x}", + task.callback.as_ref().unwrap().0 as usize, + task.callback.as_ref().unwrap().1 as usize + ); + } + count_events += 1; + task.callback + .take_if(|CallbackEntry(f, data)| matches!((f)(*data), CallbackState::Ready)); + } else { + count_waiting += 1; + match &task.inner { + EventType::Triggered { + last_counter: _, + event: _, + } => { + ws.register(task.event_fd); + } + EventType::SystemTime(system_time) => { + if *system_time > now { + let diff = system_time.duration_since(now).unwrap_or_default(); + ws.timeout(diff); + } else { + task.callback.take_if(|CallbackEntry(f, data)| { + matches!((f)(*data), CallbackState::Ready) + }); + } + } + } + } + }); + let old_busy = EXECUTOR_BUSY.swap(false, Ordering::Release); + assert!(old_busy); + ex.active_tasks.retain(|task| task.callback.is_some()); + (count_events, count_waiting) + } +} + +static EXECUTOR: Mutex = Mutex::new(Executor { + active_tasks: Vec::new(), + change_event: None, +}); +// while executing tasks from the loop we can't directly queue new ones +static EXECUTOR_BUSY: AtomicBool = AtomicBool::new(false); +static NEW_TASKS: Mutex> = Mutex::new(Vec::new()); + +impl symmetric_executor::Guest for Guest { + type CallbackFunction = Ignore; + type CallbackData = OpaqueData; + type CallbackRegistration = CallbackRegistrationInternal; + type EventSubscription = EventSubscriptionInternal; + type EventGenerator = EventGenerator; + + fn run() { + let change_event = EXECUTOR.lock().unwrap().change_event(); + loop { + let mut ws = WaitSet::new(Some(change_event)); + let (count_events, count_waiting) = { + let mut ex = EXECUTOR.lock().unwrap(); + let (count_events, count_waiting) = Executor::tick(&mut ex, &mut ws); + { + let mut new_tasks = NEW_TASKS.lock().unwrap(); + if !new_tasks.is_empty() { + ex.active_tasks.append(&mut new_tasks); + // collect callbacks and timeouts again + continue; + } + } + if ex.active_tasks.is_empty() { + break; + } + (count_events, count_waiting) + }; + if count_events != 0 { + // we processed events, perhaps more became ready + if DEBUGGING { + println!( + "Relooping with {} tasks after {count_events} events, {count_waiting} waiting", + EXECUTOR.lock().unwrap().active_tasks.len() + ); + } + continue; + } + // with no work left the break should have occured + // assert!(!tvptr.is_null() || maxfd > 0); + if DEBUGGING { + ws.debug(); + } + let selectresult = ws.wait(); + // we could look directly for the timeout + if selectresult > 0 { + // reset active file descriptors + for i in ws.iter_active() { + event_fd::consume(i); + } + } + } + } + + fn register( + trigger: symmetric_executor::EventSubscription, + callback: symmetric_executor::CallbackFunction, + data: symmetric_executor::CallbackData, + ) -> symmetric_executor::CallbackRegistration { + let cb: CallbackType = unsafe { transmute(callback.take_handle()) }; + let data = data.take_handle() as *mut OpaqueData; + + // try to take a short cut + let trigger: EventSubscriptionInternal = trigger.into_inner(); + if trigger.inner.ready() { + if DEBUGGING { + println!("register ready event {:x} {:x}", cb as usize, data as usize); + } + if matches!((cb)(data), CallbackState::Ready) { + println!("registration unnecessary"); + return symmetric_executor::CallbackRegistration::new( + CallbackRegistrationInternal(0), + ); + } + } + + let subscr = QueuedEvent::new(trigger, CallbackEntry(cb, data)); + let id = subscr.id; + match EXECUTOR.try_lock() { + Ok(mut lock) => { + lock.active_tasks.push(subscr); + let mut ws = WaitSet::new(None); + // process as long as there is immediate progress + loop { + let (count_events, _count_waiting) = Executor::tick(&mut lock, &mut ws); + if count_events == 0 { + break; + } + } + // wake other threads last + event_fd::activate(lock.change_event()); + } + Err(_err) => { + if EXECUTOR_BUSY.load(Ordering::Acquire) { + NEW_TASKS.lock().unwrap().push(subscr); + } else { + // actually this is unlikely, but give it a try + EXECUTOR.lock().unwrap().active_tasks.push(subscr); + } + } + } + symmetric_executor::CallbackRegistration::new(CallbackRegistrationInternal(id)) + } +} + +type Count = u32; + +struct EventInner { + counter: Count, + waiting: Vec, +} + +struct EventGenerator(Arc>); + +type CallbackType = fn(*mut OpaqueData) -> CallbackState; +struct CallbackEntry(CallbackType, *mut OpaqueData); + +unsafe impl Send for CallbackEntry {} + +struct EventSubscriptionInternal { + inner: EventType, +} + +struct QueuedEvent { + id: usize, + inner: EventType, + event_fd: EventFd, + callback: Option, +} + +static ID_SOURCE: AtomicUsize = AtomicUsize::new(1); + +impl QueuedEvent { + fn new(trigger: EventSubscriptionInternal, callback: CallbackEntry) -> Self { + let id = ID_SOURCE.fetch_add(1, Ordering::Relaxed); + let inner = trigger.inner; + let event_fd = match &inner { + EventType::Triggered { + last_counter: _, + event, + } => { + let fd = unsafe { libc::eventfd(0, libc::EFD_NONBLOCK) }; + event.lock().unwrap().waiting.push(fd); + fd + } + EventType::SystemTime(_system_time) => INVALID_FD, + }; + if DEBUGGING { + match &inner { + EventType::Triggered { + last_counter: _, + event, + } => println!( + "register(Trigger {:x} fd {event_fd}, {:x},{:x})", + Arc::as_ptr(event) as usize, + callback.0 as usize, + callback.1 as usize + ), + EventType::SystemTime(system_time) => { + let diff = match system_time.duration_since(SystemTime::now()) { + Ok(diff) => format!("{}.{}", diff.as_secs(), diff.subsec_nanos()), + Err(err) => format!("{err}"), + }; + println!( + "register(Time {}, {:x},{:x})", + diff, callback.0 as usize, callback.1 as usize + ); + } + } + } + QueuedEvent { + id, + inner, + callback: Some(callback), + event_fd, + } + } +} + +impl EventSubscriptionInternal { + fn dup(&self) -> Self { + let inner = match &self.inner { + EventType::Triggered { + last_counter: last_counter_old, + // event_fd, + event, + } => { + let new_event = Arc::clone(event); + let last_counter = last_counter_old.load(Ordering::Relaxed); + if DEBUGGING { + println!( + "dup(subscr {last_counter} {:x})", + Arc::as_ptr(&event) as usize + ); + } + EventType::Triggered { + last_counter: AtomicU32::new(last_counter), + event: new_event, + } + } + EventType::SystemTime(system_time) => EventType::SystemTime(*system_time), + }; + EventSubscriptionInternal { inner } + } +} + +impl Drop for QueuedEvent { + fn drop(&mut self) { + if let Some(cb) = &self.callback { + if DEBUGGING { + println!( + "drop() with active callback {:x},{:x}", + cb.0 as usize, cb.1 as usize + ); + } + } + match &self.inner { + EventType::Triggered { + last_counter: _, + event, + } => { + if DEBUGGING { + println!("drop(queued fd {})", self.event_fd); + } + event + .lock() + .unwrap() + .waiting + .retain(|&e| e != self.event_fd); + unsafe { libc::close(self.event_fd) }; + } + EventType::SystemTime(_system_time) => (), + } + } +} + +enum EventType { + Triggered { + last_counter: AtomicU32, + event: Arc>, + }, + SystemTime(SystemTime), +} + +impl EventType { + pub fn ready(&self) -> bool { + match self { + EventType::Triggered { + last_counter, + event, + } => { + let current_counter = event.lock().unwrap().counter; + let active = current_counter != last_counter.load(Ordering::Acquire); + if active { + last_counter.store(current_counter, Ordering::Release); + } + active + } + EventType::SystemTime(system_time) => *system_time <= SystemTime::now(), + } + } +} diff --git a/crates/symmetric_executor/symmetric_stream/Cargo.toml b/crates/symmetric_executor/symmetric_stream/Cargo.toml new file mode 100644 index 000000000..fb3b6bbbf --- /dev/null +++ b/crates/symmetric_executor/symmetric_stream/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "symmetric_stream" +version.workspace = true +edition.workspace = true + +[dependencies] +symmetric_executor = { path = ".." } +wit-bindgen-symmetric-rt = { version = "0.36.0", path = "../rust-client" } + +[dependencies.wit-bindgen] +package = "dummy-rt" +path = "../dummy-rt" + +[lib] +crate-type = ["cdylib"] + +[features] +trace = [] diff --git a/crates/symmetric_executor/symmetric_stream/build.rs b/crates/symmetric_executor/symmetric_stream/build.rs new file mode 100644 index 000000000..3096c1795 --- /dev/null +++ b/crates/symmetric_executor/symmetric_stream/build.rs @@ -0,0 +1,9 @@ +use std::env; + +fn main() { + let out = env::var_os("OUT_DIR").unwrap(); + println!( + r"cargo:rustc-link-search={}/../../../deps", + out.into_string().unwrap() + ); +} diff --git a/crates/symmetric_executor/symmetric_stream/src/lib.rs b/crates/symmetric_executor/symmetric_stream/src/lib.rs new file mode 100644 index 000000000..6c750f0a2 --- /dev/null +++ b/crates/symmetric_executor/symmetric_stream/src/lib.rs @@ -0,0 +1,217 @@ +use std::{ + ptr::null_mut, + sync::{ + atomic::{AtomicBool, AtomicIsize, AtomicPtr, AtomicUsize, Ordering}, + Arc, + }, +}; + +use stream_impl::exports::symmetric::runtime::symmetric_stream::{ + self, Address, GuestAddress, GuestBuffer, GuestStreamObj, +}; +use stream_impl::symmetric::runtime::symmetric_executor::EventGenerator; + +mod stream_impl; + +struct Guest; + +stream_impl::export!(Guest with_types_in stream_impl); + +struct Dummy; + +impl GuestAddress for Dummy {} + +struct Buffer { + addr: *mut (), + capacity: usize, + size: AtomicUsize, +} + +impl GuestBuffer for Buffer { + fn new(addr: symmetric_stream::Address, capacity: u64) -> Self { + Self { + addr: addr.take_handle() as *mut (), + size: AtomicUsize::new(0), + capacity: capacity as usize, + } + } + + fn get_address(&self) -> symmetric_stream::Address { + unsafe { Address::from_handle(self.addr as usize) } + } + + fn get_size(&self) -> u64 { + self.size.load(Ordering::Relaxed) as u64 + } + + fn set_size(&self, size: u64) -> () { + self.size.store(size as usize, Ordering::Relaxed) + } + + fn capacity(&self) -> u64 { + self.capacity as u64 + } +} + +mod results { + pub const BLOCKED: isize = -1; +} + +struct StreamInner { + read_ready_event_send: EventGenerator, + write_ready_event_send: EventGenerator, + read_addr: AtomicPtr<()>, + read_size: AtomicUsize, + ready_addr: AtomicPtr<()>, + ready_size: AtomicIsize, + ready_capacity: AtomicUsize, + // if the writer closes before the reader has consumed the last data + write_closed: AtomicBool, +} + +struct StreamObj(Arc); + +impl GuestStreamObj for StreamObj { + fn new() -> Self { + let inner = StreamInner { + read_ready_event_send: EventGenerator::new(), + write_ready_event_send: EventGenerator::new(), + read_addr: AtomicPtr::new(core::ptr::null_mut()), + read_size: AtomicUsize::new(0), + ready_addr: AtomicPtr::new(core::ptr::null_mut()), + ready_size: AtomicIsize::new(results::BLOCKED), + ready_capacity: AtomicUsize::new(0), + write_closed: AtomicBool::new(false), + }; + #[cfg(feature = "trace")] + println!("Stream::new {:x}", inner.read_ready_event_send.handle()); + Self(Arc::new(inner)) + } + + fn is_write_closed(&self) -> bool { + self.0.ready_addr.load(Ordering::Acquire) as usize == EOF_MARKER + || self.0.write_closed.load(Ordering::Acquire) + } + + fn start_reading(&self, buffer: symmetric_stream::Buffer) { + let buf = buffer.get::().get_address().take_handle() as *mut (); + let size = buffer.get::().capacity(); + #[cfg(feature = "trace")] + println!( + "Stream::start_read {:x} {buf:x?} {size} =>", + self.0.read_ready_event_send.handle() + ); + let old_readya = self.0.ready_addr.load(Ordering::Acquire); + let old_ready = self.0.ready_size.load(Ordering::Acquire); + if old_readya as usize == EOF_MARKER { + todo!(); + } + assert!(old_ready == results::BLOCKED); + let old_size = self.0.read_size.swap(size as usize, Ordering::Acquire); + assert_eq!(old_size, 0); + let old_ptr = self.0.read_addr.swap(buf, Ordering::Release); + assert_eq!(old_ptr, std::ptr::null_mut()); + self.write_ready_activate(); + } + + fn read_result(&self) -> Option { + let size = self.0.ready_size.swap(results::BLOCKED, Ordering::Acquire); + let addr = self.0.ready_addr.swap(null_mut(), Ordering::Relaxed); + let capacity = self.0.ready_capacity.swap(0, Ordering::Relaxed); + #[cfg(feature = "trace")] + println!( + "Stream::read_result {:x} {addr:x?} {size}", + self.0.read_ready_event_send.handle() + ); + if addr as usize == EOF_MARKER || (addr == null_mut() && size == results::BLOCKED) { + None + } else { + Some(symmetric_stream::Buffer::new(Buffer { + addr, + capacity, + size: AtomicUsize::new(size as usize), + })) + } + } + + fn is_ready_to_write(&self) -> bool { + !self.0.read_addr.load(Ordering::Acquire).is_null() + } + + fn start_writing(&self) -> symmetric_stream::Buffer { + let size = self.0.read_size.swap(0, Ordering::Acquire); + let addr = self + .0 + .read_addr + .swap(core::ptr::null_mut(), Ordering::Relaxed); + #[cfg(feature = "trace")] + println!( + "Stream::start_write {:x} {addr:x?} {size}", + self.0.read_ready_event_send.handle() + ); + self.0.ready_capacity.store(size, Ordering::Release); + symmetric_stream::Buffer::new(Buffer { + addr, + capacity: size, + size: AtomicUsize::new(0), + }) + } + + fn finish_writing(&self, buffer: Option) -> () { + let (elements, addr) = if let Some(buffer) = buffer { + let elements = buffer.get::().get_size() as isize; + let addr = buffer.get::().get_address().take_handle() as *mut (); + (elements, addr) + } else { + if self.is_write_closed() { + todo!("double close"); + } + if !self.0.ready_addr.load(Ordering::Relaxed).is_null() { + self.0.write_closed.store(true, Ordering::Release); + #[cfg(feature = "trace")] + println!("Stream::finish_write CLOSE {:x}", self.0.read_ready_event_send.handle()); + return; + } + (0, EOF_MARKER as usize as *mut ()) + }; + #[cfg(feature = "trace")] + println!( + "Stream::finish_write {:x} {addr:x?} {elements} =>", + self.0.read_ready_event_send.handle() + ); + let old_ready = self.0.ready_size.swap(elements as isize, Ordering::Relaxed); + let _old_readya = self.0.ready_addr.swap(addr, Ordering::Release); + assert_eq!(old_ready, results::BLOCKED); + self.read_ready_activate(); + } + + fn clone(&self) -> symmetric_stream::StreamObj { + symmetric_stream::StreamObj::new(StreamObj(Arc::clone(&self.0))) + } + + fn write_ready_activate(&self) { + self.0.write_ready_event_send.activate(); + } + + fn read_ready_subscribe(&self) -> symmetric_stream::EventSubscription { + self.0.read_ready_event_send.subscribe() + } + + fn write_ready_subscribe(&self) -> symmetric_stream::EventSubscription { + self.0.write_ready_event_send.subscribe() + } + + fn read_ready_activate(&self) { + self.0.read_ready_event_send.activate(); + } +} + +const EOF_MARKER: usize = 1; + +impl symmetric_stream::Guest for Guest { + type Address = Dummy; + + type Buffer = Buffer; + + type StreamObj = StreamObj; +} diff --git a/crates/symmetric_executor/symmetric_stream/src/stream_impl.rs b/crates/symmetric_executor/symmetric_stream/src/stream_impl.rs new file mode 100644 index 000000000..686ab7093 --- /dev/null +++ b/crates/symmetric_executor/symmetric_stream/src/stream_impl.rs @@ -0,0 +1,1706 @@ +// Generated by `wit-bindgen` 0.41.0. DO NOT EDIT! +// Options used: +#[allow(dead_code, clippy::all)] +pub mod symmetric { + pub mod runtime { + /// This interface will only work with symmetric ABI (shared everything), + /// it can't be composed with the canonical ABI + /// Asynchronous executor functionality for symmetric ABI + #[allow(dead_code, async_fn_in_trait, unused_imports, clippy::all)] + pub mod symmetric_executor { + #[used] + #[doc(hidden)] + static __FORCE_SECTION_REF: fn() = + super::super::super::__link_custom_section_describing_imports; + + use super::super::super::_rt; + /// These pseudo-resources are just used to + /// pass pointers to register + /// Return value of an event callback + #[repr(u8)] + #[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)] + pub enum CallbackState { + /// Call the function again + Pending, + /// The function has completed, all results are written, data is freed, + /// calling the function again is not permitted as data became invalid! + Ready, + } + impl ::core::fmt::Debug for CallbackState { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + CallbackState::Pending => f.debug_tuple("CallbackState::Pending").finish(), + CallbackState::Ready => f.debug_tuple("CallbackState::Ready").finish(), + } + } + } + + impl CallbackState { + #[doc(hidden)] + pub unsafe fn _lift(val: u8) -> CallbackState { + if !cfg!(debug_assertions) { + return unsafe { ::core::mem::transmute(val) }; + } + + match val { + 0 => CallbackState::Pending, + 1 => CallbackState::Ready, + + _ => panic!("invalid enum discriminant"), + } + } + } + + /// This wraps a user provided function of type + /// `fn (callback-data) -> callback-state` + + #[derive(Debug)] + #[repr(transparent)] + pub struct CallbackFunction { + handle: _rt::Resource, + } + + impl CallbackFunction { + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + Self { + handle: unsafe { _rt::Resource::from_handle(handle) }, + } + } + + #[doc(hidden)] + pub fn take_handle(&self) -> usize { + _rt::Resource::take_handle(&self.handle) + } + + #[doc(hidden)] + pub fn handle(&self) -> usize { + _rt::Resource::handle(&self.handle) + } + } + + unsafe impl _rt::WasmResource for CallbackFunction { + #[inline] + unsafe fn drop(_handle: usize) { + { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[resource-drop]callback-function" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Dcallback_function( + _: usize, + ); + } + + unsafe { + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Dcallback_function(_handle) + }; + } + } + } + + /// This wraps opaque user data, freed by the callback when + /// it returns ready + + #[derive(Debug)] + #[repr(transparent)] + pub struct CallbackData { + handle: _rt::Resource, + } + + impl CallbackData { + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + Self { + handle: unsafe { _rt::Resource::from_handle(handle) }, + } + } + + #[doc(hidden)] + pub fn take_handle(&self) -> usize { + _rt::Resource::take_handle(&self.handle) + } + + #[doc(hidden)] + pub fn handle(&self) -> usize { + _rt::Resource::handle(&self.handle) + } + } + + unsafe impl _rt::WasmResource for CallbackData { + #[inline] + unsafe fn drop(_handle: usize) { + { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[resource-drop]callback-data" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Dcallback_data( + _: usize, + ); + } + + unsafe { + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Dcallback_data(_handle) + }; + } + } + } + + /// The receiving side of an event + + #[derive(Debug)] + #[repr(transparent)] + pub struct EventSubscription { + handle: _rt::Resource, + } + + impl EventSubscription { + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + Self { + handle: unsafe { _rt::Resource::from_handle(handle) }, + } + } + + #[doc(hidden)] + pub fn take_handle(&self) -> usize { + _rt::Resource::take_handle(&self.handle) + } + + #[doc(hidden)] + pub fn handle(&self) -> usize { + _rt::Resource::handle(&self.handle) + } + } + + unsafe impl _rt::WasmResource for EventSubscription { + #[inline] + unsafe fn drop(_handle: usize) { + { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[resource-drop]event-subscription" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Devent_subscription( + _: usize, + ); + } + + unsafe { + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Devent_subscription(_handle) + }; + } + } + } + + /// A user controlled event + + #[derive(Debug)] + #[repr(transparent)] + pub struct EventGenerator { + handle: _rt::Resource, + } + + impl EventGenerator { + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + Self { + handle: unsafe { _rt::Resource::from_handle(handle) }, + } + } + + #[doc(hidden)] + pub fn take_handle(&self) -> usize { + _rt::Resource::take_handle(&self.handle) + } + + #[doc(hidden)] + pub fn handle(&self) -> usize { + _rt::Resource::handle(&self.handle) + } + } + + unsafe impl _rt::WasmResource for EventGenerator { + #[inline] + unsafe fn drop(_handle: usize) { + { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[resource-drop]event-generator" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Devent_generator( + _: usize, + ); + } + + unsafe { + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Devent_generator(_handle) + }; + } + } + } + + /// Handle to cancel a callback registration + + #[derive(Debug)] + #[repr(transparent)] + pub struct CallbackRegistration { + handle: _rt::Resource, + } + + impl CallbackRegistration { + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + Self { + handle: unsafe { _rt::Resource::from_handle(handle) }, + } + } + + #[doc(hidden)] + pub fn take_handle(&self) -> usize { + _rt::Resource::take_handle(&self.handle) + } + + #[doc(hidden)] + pub fn handle(&self) -> usize { + _rt::Resource::handle(&self.handle) + } + } + + unsafe impl _rt::WasmResource for CallbackRegistration { + #[inline] + unsafe fn drop(_handle: usize) { + { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[resource-drop]callback-registration" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Dcallback_registration( + _: usize, + ); + } + + unsafe { + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5Bresource_dropX5Dcallback_registration(_handle) + }; + } + } + } + + /// Return value of an async call, lowest bit encoding + #[repr(u8)] + #[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)] + pub enum CallStatus { + /// For symmetric this means that processing has started, parameters should still remain valid until null, + /// params-read = non-null, results-written,done = null + Started, + /// For symmetric: Retry the call (temporarily out of memory) + NotStarted, + } + impl ::core::fmt::Debug for CallStatus { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + CallStatus::Started => f.debug_tuple("CallStatus::Started").finish(), + CallStatus::NotStarted => f.debug_tuple("CallStatus::NotStarted").finish(), + } + } + } + + impl CallStatus { + #[doc(hidden)] + pub unsafe fn _lift(val: u8) -> CallStatus { + if !cfg!(debug_assertions) { + return unsafe { ::core::mem::transmute(val) }; + } + + match val { + 0 => CallStatus::Started, + 1 => CallStatus::NotStarted, + + _ => panic!("invalid enum discriminant"), + } + } + } + + impl EventSubscription { + #[allow(unused_unsafe, clippy::all)] + /// Whether the event is active (used by poll implementation) + #[allow(async_fn_in_trait)] + pub fn ready(&self) -> bool { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[method]event-subscription.ready" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_subscriptionX2Eready( + _: *mut u8, + ) -> i32; + } + let ret = symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_subscriptionX2Eready((self).handle() as *mut u8); + _rt::bool_lift(ret as u8) + } + } + } + impl EventSubscription { + #[allow(unused_unsafe, clippy::all)] + /// Create a timeout event + #[allow(async_fn_in_trait)] + pub fn from_timeout(nanoseconds: u64) -> EventSubscription { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[static]event-subscription.from-timeout" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BstaticX5Devent_subscriptionX2Efrom_timeout( + _: i64, + ) -> *mut u8; + } + let ret = symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BstaticX5Devent_subscriptionX2Efrom_timeout(_rt::as_i64(&nanoseconds)); + EventSubscription::from_handle(ret as usize) + } + } + } + impl EventSubscription { + #[allow(unused_unsafe, clippy::all)] + /// Duplicate the subscription (e.g. for repeated callback registering, same cost as subscribe) + #[allow(async_fn_in_trait)] + pub fn dup(&self) -> EventSubscription { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[method]event-subscription.dup" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_subscriptionX2Edup( + _: *mut u8, + ) -> *mut u8; + } + let ret = symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_subscriptionX2Edup((self).handle() as *mut u8); + EventSubscription::from_handle(ret as usize) + } + } + } + impl EventSubscription { + #[allow(unused_unsafe, clippy::all)] + /// Reset subscription to be inactive, only next trigger will ready it + #[allow(async_fn_in_trait)] + pub fn reset(&self) -> () { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[method]event-subscription.reset" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_subscriptionX2Ereset( + _: *mut u8, + ); + } + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_subscriptionX2Ereset((self).handle() as *mut u8); + } + } + } + impl EventGenerator { + #[allow(unused_unsafe, clippy::all)] + #[allow(async_fn_in_trait)] + pub fn new() -> Self { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[constructor]event-generator" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BconstructorX5Devent_generator( + ) -> *mut u8; + } + let ret = symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BconstructorX5Devent_generator(); + EventGenerator::from_handle(ret as usize) + } + } + } + impl EventGenerator { + #[allow(unused_unsafe, clippy::all)] + /// Get the receiving side (to pass to other parts of the program) + #[allow(async_fn_in_trait)] + pub fn subscribe(&self) -> EventSubscription { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[method]event-generator.subscribe" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_generatorX2Esubscribe( + _: *mut u8, + ) -> *mut u8; + } + let ret = symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_generatorX2Esubscribe((self).handle() as *mut u8); + EventSubscription::from_handle(ret as usize) + } + } + } + impl EventGenerator { + #[allow(unused_unsafe, clippy::all)] + /// Trigger all subscribers + #[allow(async_fn_in_trait)] + pub fn activate(&self) -> () { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[method]event-generator.activate" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_generatorX2Eactivate( + _: *mut u8, + ); + } + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BmethodX5Devent_generatorX2Eactivate((self).handle() as *mut u8); + } + } + } + impl CallbackRegistration { + #[allow(unused_unsafe, clippy::all)] + /// returns the data passed to the registration + #[allow(async_fn_in_trait)] + pub fn cancel(obj: CallbackRegistration) -> CallbackData { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[static]callback-registration.cancel" + )] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BstaticX5Dcallback_registrationX2Ecancel( + _: *mut u8, + ) -> *mut u8; + } + let ret = symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00X5BstaticX5Dcallback_registrationX2Ecancel((&obj).take_handle() as *mut u8); + CallbackData::from_handle(ret as usize) + } + } + } + #[allow(unused_unsafe, clippy::all)] + /// Wait until all registered events have completed + #[allow(async_fn_in_trait)] + pub fn run() -> () { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "run")] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00run(); + } + symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00run(); + } + } + #[allow(unused_unsafe, clippy::all)] + /// Register a callback for an event + #[allow(async_fn_in_trait)] + pub fn register( + trigger: EventSubscription, + callback: CallbackFunction, + data: CallbackData, + ) -> CallbackRegistration { + unsafe { + #[link(wasm_import_module = "symmetric:runtime/symmetric-executor@0.2.1")] + unsafe extern "C" { + #[cfg_attr(target_arch = "wasm32", link_name = "register")] + fn symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00register( + _: *mut u8, + _: *mut u8, + _: *mut u8, + ) -> *mut u8; + } + let ret = symmetricX3AruntimeX2Fsymmetric_executorX400X2E2X2E1X00register( + (&trigger).take_handle() as *mut u8, + (&callback).take_handle() as *mut u8, + (&data).take_handle() as *mut u8, + ); + CallbackRegistration::from_handle(ret as usize) + } + } + } + } +} +#[allow(dead_code, clippy::all)] +pub mod exports { + pub mod symmetric { + pub mod runtime { + /// language neutral stream implementation + #[allow(dead_code, async_fn_in_trait, unused_imports, clippy::all)] + pub mod symmetric_stream { + #[used] + #[doc(hidden)] + static __FORCE_SECTION_REF: fn() = + super::super::super::super::__link_custom_section_describing_imports; + + use super::super::super::super::_rt; + pub type EventSubscription = super::super::super::super::symmetric::runtime::symmetric_executor::EventSubscription; + + #[derive(Debug)] + #[repr(transparent)] + pub struct Address { + handle: _rt::Resource
, + } + + type _AddressRep = Option; + + impl Address { + /// Creates a new resource from the specified representation. + /// + /// This function will create a new resource handle by moving `val` onto + /// the heap and then passing that heap pointer to the component model to + /// create a handle. The owned handle is then returned as `Address`. + pub fn new(val: T) -> Self { + Self::type_guard::(); + let val: _AddressRep = Some(val); + let ptr: *mut _AddressRep = _rt::Box::into_raw(_rt::Box::new(val)); + unsafe { Self::from_handle(T::_resource_new(ptr.cast())) } + } + + /// Gets access to the underlying `T` which represents this resource. + pub fn get(&self) -> &T { + let ptr = unsafe { &*self.as_ptr::() }; + ptr.as_ref().unwrap() + } + + /// Gets mutable access to the underlying `T` which represents this + /// resource. + pub fn get_mut(&mut self) -> &mut T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.as_mut().unwrap() + } + + /// Consumes this resource and returns the underlying `T`. + pub fn into_inner(self) -> T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.take().unwrap() + } + + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + Self { + handle: unsafe { _rt::Resource::from_handle(handle) }, + } + } + + #[doc(hidden)] + pub fn take_handle(&self) -> usize { + _rt::Resource::take_handle(&self.handle) + } + + #[doc(hidden)] + pub fn handle(&self) -> usize { + _rt::Resource::handle(&self.handle) + } + + // It's theoretically possible to implement the `GuestAddress` trait twice + // so guard against using it with two different types here. + #[doc(hidden)] + fn type_guard() { + use core::any::TypeId; + static mut LAST_TYPE: Option = None; + unsafe { + assert!(!cfg!(target_feature = "atomics")); + let id = TypeId::of::(); + match LAST_TYPE { + Some(ty) => assert!( + ty == id, + "cannot use two types with this resource type" + ), + None => LAST_TYPE = Some(id), + } + } + } + + #[doc(hidden)] + pub unsafe fn dtor(handle: *mut u8) { + Self::type_guard::(); + let _ = unsafe { _rt::Box::from_raw(handle as *mut _AddressRep) }; + } + + fn as_ptr(&self) -> *mut _AddressRep { + Address::type_guard::(); + T::_resource_rep(self.handle()).cast() + } + } + + /// A borrowed version of [`Address`] which represents a borrowed value + /// with the lifetime `'a`. + #[derive(Debug)] + #[repr(transparent)] + pub struct AddressBorrow<'a> { + rep: *mut u8, + _marker: core::marker::PhantomData<&'a Address>, + } + + impl<'a> AddressBorrow<'a> { + #[doc(hidden)] + pub unsafe fn lift(rep: usize) -> Self { + Self { + rep: rep as *mut u8, + _marker: core::marker::PhantomData, + } + } + + /// Gets access to the underlying `T` in this resource. + pub fn get(&self) -> &T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.as_ref().unwrap() + } + + // NB: mutable access is not allowed due to the component model allowing + // multiple borrows of the same resource. + + fn as_ptr(&self) -> *mut _AddressRep { + Address::type_guard::(); + self.rep.cast() + } + } + + unsafe impl _rt::WasmResource for Address { + #[inline] + unsafe fn drop(_handle: usize) { + { + #[link(wasm_import_module = "symmetric:runtime/symmetric-stream@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[resource-drop]address" + )] + fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5Bresource_dropX5Daddress( + _: usize, + ); + } + + unsafe { + symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5Bresource_dropX5Daddress(_handle) + }; + } + } + } + + /// special zero allocation/copy data type (caller provided buffer) + + #[derive(Debug)] + #[repr(transparent)] + pub struct Buffer { + handle: _rt::Resource, + } + + type _BufferRep = Option; + + impl Buffer { + /// Creates a new resource from the specified representation. + /// + /// This function will create a new resource handle by moving `val` onto + /// the heap and then passing that heap pointer to the component model to + /// create a handle. The owned handle is then returned as `Buffer`. + pub fn new(val: T) -> Self { + Self::type_guard::(); + let val: _BufferRep = Some(val); + let ptr: *mut _BufferRep = _rt::Box::into_raw(_rt::Box::new(val)); + unsafe { Self::from_handle(T::_resource_new(ptr.cast())) } + } + + /// Gets access to the underlying `T` which represents this resource. + pub fn get(&self) -> &T { + let ptr = unsafe { &*self.as_ptr::() }; + ptr.as_ref().unwrap() + } + + /// Gets mutable access to the underlying `T` which represents this + /// resource. + pub fn get_mut(&mut self) -> &mut T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.as_mut().unwrap() + } + + /// Consumes this resource and returns the underlying `T`. + pub fn into_inner(self) -> T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.take().unwrap() + } + + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + Self { + handle: unsafe { _rt::Resource::from_handle(handle) }, + } + } + + #[doc(hidden)] + pub fn take_handle(&self) -> usize { + _rt::Resource::take_handle(&self.handle) + } + + #[doc(hidden)] + pub fn handle(&self) -> usize { + _rt::Resource::handle(&self.handle) + } + + // It's theoretically possible to implement the `GuestBuffer` trait twice + // so guard against using it with two different types here. + #[doc(hidden)] + fn type_guard() { + use core::any::TypeId; + static mut LAST_TYPE: Option = None; + unsafe { + assert!(!cfg!(target_feature = "atomics")); + let id = TypeId::of::(); + match LAST_TYPE { + Some(ty) => assert!( + ty == id, + "cannot use two types with this resource type" + ), + None => LAST_TYPE = Some(id), + } + } + } + + #[doc(hidden)] + pub unsafe fn dtor(handle: *mut u8) { + Self::type_guard::(); + let _ = unsafe { _rt::Box::from_raw(handle as *mut _BufferRep) }; + } + + fn as_ptr(&self) -> *mut _BufferRep { + Buffer::type_guard::(); + T::_resource_rep(self.handle()).cast() + } + } + + /// A borrowed version of [`Buffer`] which represents a borrowed value + /// with the lifetime `'a`. + #[derive(Debug)] + #[repr(transparent)] + pub struct BufferBorrow<'a> { + rep: *mut u8, + _marker: core::marker::PhantomData<&'a Buffer>, + } + + impl<'a> BufferBorrow<'a> { + #[doc(hidden)] + pub unsafe fn lift(rep: usize) -> Self { + Self { + rep: rep as *mut u8, + _marker: core::marker::PhantomData, + } + } + + /// Gets access to the underlying `T` in this resource. + pub fn get(&self) -> &T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.as_ref().unwrap() + } + + // NB: mutable access is not allowed due to the component model allowing + // multiple borrows of the same resource. + + fn as_ptr(&self) -> *mut _BufferRep { + Buffer::type_guard::(); + self.rep.cast() + } + } + + unsafe impl _rt::WasmResource for Buffer { + #[inline] + unsafe fn drop(_handle: usize) { + { + #[link(wasm_import_module = "symmetric:runtime/symmetric-stream@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[resource-drop]buffer" + )] + fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5Bresource_dropX5Dbuffer( + _: usize, + ); + } + + unsafe { + symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5Bresource_dropX5Dbuffer(_handle) + }; + } + } + } + + #[derive(Debug)] + #[repr(transparent)] + pub struct StreamObj { + handle: _rt::Resource, + } + + type _StreamObjRep = Option; + + impl StreamObj { + /// Creates a new resource from the specified representation. + /// + /// This function will create a new resource handle by moving `val` onto + /// the heap and then passing that heap pointer to the component model to + /// create a handle. The owned handle is then returned as `StreamObj`. + pub fn new(val: T) -> Self { + Self::type_guard::(); + let val: _StreamObjRep = Some(val); + let ptr: *mut _StreamObjRep = _rt::Box::into_raw(_rt::Box::new(val)); + unsafe { Self::from_handle(T::_resource_new(ptr.cast())) } + } + + /// Gets access to the underlying `T` which represents this resource. + pub fn get(&self) -> &T { + let ptr = unsafe { &*self.as_ptr::() }; + ptr.as_ref().unwrap() + } + + /// Gets mutable access to the underlying `T` which represents this + /// resource. + pub fn get_mut(&mut self) -> &mut T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.as_mut().unwrap() + } + + /// Consumes this resource and returns the underlying `T`. + pub fn into_inner(self) -> T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.take().unwrap() + } + + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + Self { + handle: unsafe { _rt::Resource::from_handle(handle) }, + } + } + + #[doc(hidden)] + pub fn take_handle(&self) -> usize { + _rt::Resource::take_handle(&self.handle) + } + + #[doc(hidden)] + pub fn handle(&self) -> usize { + _rt::Resource::handle(&self.handle) + } + + // It's theoretically possible to implement the `GuestStreamObj` trait twice + // so guard against using it with two different types here. + #[doc(hidden)] + fn type_guard() { + use core::any::TypeId; + static mut LAST_TYPE: Option = None; + unsafe { + assert!(!cfg!(target_feature = "atomics")); + let id = TypeId::of::(); + match LAST_TYPE { + Some(ty) => assert!( + ty == id, + "cannot use two types with this resource type" + ), + None => LAST_TYPE = Some(id), + } + } + } + + #[doc(hidden)] + pub unsafe fn dtor(handle: *mut u8) { + Self::type_guard::(); + let _ = unsafe { _rt::Box::from_raw(handle as *mut _StreamObjRep) }; + } + + fn as_ptr(&self) -> *mut _StreamObjRep { + StreamObj::type_guard::(); + T::_resource_rep(self.handle()).cast() + } + } + + /// A borrowed version of [`StreamObj`] which represents a borrowed value + /// with the lifetime `'a`. + #[derive(Debug)] + #[repr(transparent)] + pub struct StreamObjBorrow<'a> { + rep: *mut u8, + _marker: core::marker::PhantomData<&'a StreamObj>, + } + + impl<'a> StreamObjBorrow<'a> { + #[doc(hidden)] + pub unsafe fn lift(rep: usize) -> Self { + Self { + rep: rep as *mut u8, + _marker: core::marker::PhantomData, + } + } + + /// Gets access to the underlying `T` in this resource. + pub fn get(&self) -> &T { + let ptr = unsafe { &mut *self.as_ptr::() }; + ptr.as_ref().unwrap() + } + + // NB: mutable access is not allowed due to the component model allowing + // multiple borrows of the same resource. + + fn as_ptr(&self) -> *mut _StreamObjRep { + StreamObj::type_guard::(); + self.rep.cast() + } + } + + unsafe impl _rt::WasmResource for StreamObj { + #[inline] + unsafe fn drop(_handle: usize) { + { + #[link(wasm_import_module = "symmetric:runtime/symmetric-stream@0.2.1")] + unsafe extern "C" { + #[cfg_attr( + target_arch = "wasm32", + link_name = "[resource-drop]stream-obj" + )] + fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5Bresource_dropX5Dstream_obj( + _: usize, + ); + } + + unsafe { + symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5Bresource_dropX5Dstream_obj(_handle) + }; + } + } + } + + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_constructor_buffer_cabi( + arg0: *mut u8, + arg1: i64, + ) -> *mut u8 { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let result0 = { + Buffer::new(T::new(Address::from_handle(arg0 as usize), arg1 as u64)) + }; + (result0).take_handle() as *mut u8 + } + } + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_method_buffer_get_address_cabi( + arg0: *mut u8, + ) -> *mut u8 { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let result0 = { T::get_address(BufferBorrow::lift(arg0 as usize).get()) }; + (result0).take_handle() as *mut u8 + } + } + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_method_buffer_get_size_cabi( + arg0: *mut u8, + ) -> i64 { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let result0 = { T::get_size(BufferBorrow::lift(arg0 as usize).get()) }; + _rt::as_i64(result0) + } + } + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_method_buffer_set_size_cabi( + arg0: *mut u8, + arg1: i64, + ) { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + { + T::set_size(BufferBorrow::lift(arg0 as usize).get(), arg1 as u64) + }; + } + } + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_method_buffer_capacity_cabi( + arg0: *mut u8, + ) -> i64 { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let result0 = { T::capacity(BufferBorrow::lift(arg0 as usize).get()) }; + _rt::as_i64(result0) + } + } + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_constructor_stream_obj_cabi() -> *mut u8 { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let result0 = { StreamObj::new(T::new()) }; + (result0).take_handle() as *mut u8 + } + } + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_method_stream_obj_clone_cabi( + arg0: *mut u8, + ) -> *mut u8 { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let result0 = { T::clone(StreamObjBorrow::lift(arg0 as usize).get()) }; + (result0).take_handle() as *mut u8 + } + } + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_method_stream_obj_is_write_closed_cabi( + arg0: *mut u8, + ) -> i32 { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let result0 = + { T::is_write_closed(StreamObjBorrow::lift(arg0 as usize).get()) }; + match result0 { + true => 1, + false => 0, + } + } + } + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_method_stream_obj_start_reading_cabi( + arg0: *mut u8, + arg1: *mut u8, + ) { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + { + T::start_reading( + StreamObjBorrow::lift(arg0 as usize).get(), + Buffer::from_handle(arg1 as usize), + ) + }; + } + } + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_method_stream_obj_write_ready_activate_cabi< + T: GuestStreamObj, + >( + arg0: *mut u8, + ) { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + { + T::write_ready_activate(StreamObjBorrow::lift(arg0 as usize).get()) + }; + } + } + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_method_stream_obj_read_ready_subscribe_cabi< + T: GuestStreamObj, + >( + arg0: *mut u8, + ) -> *mut u8 { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let result0 = + { T::read_ready_subscribe(StreamObjBorrow::lift(arg0 as usize).get()) }; + (result0).take_handle() as *mut u8 + } + } + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_method_stream_obj_read_result_cabi( + arg0: *mut u8, + arg1: *mut u8, + ) { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let result0 = + { T::read_result(StreamObjBorrow::lift(arg0 as usize).get()) }; + match result0 { + Some(e) => { + *arg1.add(0).cast::() = (1i32) as u8; + *arg1 + .add(::core::mem::size_of::<*const u8>()) + .cast::<*mut u8>() = (e).take_handle() as *mut u8; + } + None => { + *arg1.add(0).cast::() = (0i32) as u8; + } + }; + } + } + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_method_stream_obj_is_ready_to_write_cabi< + T: GuestStreamObj, + >( + arg0: *mut u8, + ) -> i32 { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let result0 = + { T::is_ready_to_write(StreamObjBorrow::lift(arg0 as usize).get()) }; + match result0 { + true => 1, + false => 0, + } + } + } + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_method_stream_obj_write_ready_subscribe_cabi< + T: GuestStreamObj, + >( + arg0: *mut u8, + ) -> *mut u8 { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let result0 = { + T::write_ready_subscribe(StreamObjBorrow::lift(arg0 as usize).get()) + }; + (result0).take_handle() as *mut u8 + } + } + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_method_stream_obj_start_writing_cabi( + arg0: *mut u8, + ) -> *mut u8 { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let result0 = + { T::start_writing(StreamObjBorrow::lift(arg0 as usize).get()) }; + (result0).take_handle() as *mut u8 + } + } + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_method_stream_obj_finish_writing_cabi( + arg0: *mut u8, + arg1: i32, + arg2: *mut u8, + ) { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + { + T::finish_writing( + StreamObjBorrow::lift(arg0 as usize).get(), + match arg1 { + 0 => None, + 1 => { + let e = Buffer::from_handle(arg2 as usize); + Some(e) + } + _ => _rt::invalid_enum_discriminant(), + }, + ) + }; + } + } + #[doc(hidden)] + #[allow(non_snake_case, unused_unsafe)] + pub unsafe fn _export_method_stream_obj_read_ready_activate_cabi< + T: GuestStreamObj, + >( + arg0: *mut u8, + ) { + unsafe { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + { + T::read_ready_activate(StreamObjBorrow::lift(arg0 as usize).get()) + }; + } + } + pub trait Guest { + type Address: GuestAddress; + type Buffer: GuestBuffer; + type StreamObj: GuestStreamObj; + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_drop_address_cabi(arg0: usize) { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + Address::dtor::(arg0 as *mut u8); + } + pub trait GuestAddress: 'static { + #[doc(hidden)] + unsafe fn _resource_new(val: *mut u8) -> usize + where + Self: Sized, + { + val as usize + } + + #[doc(hidden)] + fn _resource_rep(handle: usize) -> *mut u8 + where + Self: Sized, + { + handle as *mut u8 + } + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_drop_buffer_cabi(arg0: usize) { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + Buffer::dtor::(arg0 as *mut u8); + } + pub trait GuestBuffer: 'static { + #[doc(hidden)] + unsafe fn _resource_new(val: *mut u8) -> usize + where + Self: Sized, + { + val as usize + } + + #[doc(hidden)] + fn _resource_rep(handle: usize) -> *mut u8 + where + Self: Sized, + { + handle as *mut u8 + } + + #[allow(async_fn_in_trait)] + fn new(addr: Address, capacity: u64) -> Self; + #[allow(async_fn_in_trait)] + fn get_address(&self) -> Address; + #[allow(async_fn_in_trait)] + fn get_size(&self) -> u64; + #[allow(async_fn_in_trait)] + fn set_size(&self, size: u64) -> (); + #[allow(async_fn_in_trait)] + fn capacity(&self) -> u64; + } + #[doc(hidden)] + #[allow(non_snake_case)] + pub unsafe fn _export_drop_streamObj_cabi(arg0: usize) { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + StreamObj::dtor::(arg0 as *mut u8); + } + pub trait GuestStreamObj: 'static { + #[doc(hidden)] + unsafe fn _resource_new(val: *mut u8) -> usize + where + Self: Sized, + { + val as usize + } + + #[doc(hidden)] + fn _resource_rep(handle: usize) -> *mut u8 + where + Self: Sized, + { + handle as *mut u8 + } + + #[allow(async_fn_in_trait)] + fn new() -> Self; + /// create a new instance e.g. for reading or tasks + #[allow(async_fn_in_trait)] + fn clone(&self) -> StreamObj; + /// reading (in roughly chronological order) + #[allow(async_fn_in_trait)] + fn is_write_closed(&self) -> bool; + #[allow(async_fn_in_trait)] + fn start_reading(&self, buffer: Buffer) -> (); + #[allow(async_fn_in_trait)] + fn write_ready_activate(&self) -> (); + #[allow(async_fn_in_trait)] + fn read_ready_subscribe(&self) -> EventSubscription; + /// none is EOF + #[allow(async_fn_in_trait)] + fn read_result(&self) -> Option; + /// writing + #[allow(async_fn_in_trait)] + fn is_ready_to_write(&self) -> bool; + #[allow(async_fn_in_trait)] + fn write_ready_subscribe(&self) -> EventSubscription; + #[allow(async_fn_in_trait)] + fn start_writing(&self) -> Buffer; + /// none is EOF + #[allow(async_fn_in_trait)] + fn finish_writing(&self, buffer: Option) -> (); + #[allow(async_fn_in_trait)] + fn read_ready_activate(&self) -> (); + } + #[doc(hidden)] + + macro_rules! __export_symmetric_runtime_symmetric_stream_0_2_1_cabi{ + ($ty:ident with_types_in $($path_to_types:tt)*) => (const _: () = { + + #[cfg_attr(target_arch = "wasm32", export_name = "[constructor]buffer")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BconstructorX5Dbuffer(arg0: *mut u8,arg1: i64,) -> *mut u8 { + unsafe { $($path_to_types)*::_export_constructor_buffer_cabi::<<$ty as $($path_to_types)*::Guest>::Buffer>(arg0, arg1) } + } + #[cfg_attr(target_arch = "wasm32", export_name = "[method]buffer.get-address")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5DbufferX2Eget_address(arg0: *mut u8,) -> *mut u8 { + unsafe { $($path_to_types)*::_export_method_buffer_get_address_cabi::<<$ty as $($path_to_types)*::Guest>::Buffer>(arg0) } + } + #[cfg_attr(target_arch = "wasm32", export_name = "[method]buffer.get-size")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5DbufferX2Eget_size(arg0: *mut u8,) -> i64 { + unsafe { $($path_to_types)*::_export_method_buffer_get_size_cabi::<<$ty as $($path_to_types)*::Guest>::Buffer>(arg0) } + } + #[cfg_attr(target_arch = "wasm32", export_name = "[method]buffer.set-size")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5DbufferX2Eset_size(arg0: *mut u8,arg1: i64,) { + unsafe { $($path_to_types)*::_export_method_buffer_set_size_cabi::<<$ty as $($path_to_types)*::Guest>::Buffer>(arg0, arg1) } + } + #[cfg_attr(target_arch = "wasm32", export_name = "[method]buffer.capacity")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5DbufferX2Ecapacity(arg0: *mut u8,) -> i64 { + unsafe { $($path_to_types)*::_export_method_buffer_capacity_cabi::<<$ty as $($path_to_types)*::Guest>::Buffer>(arg0) } + } + #[cfg_attr(target_arch = "wasm32", export_name = "[constructor]stream-obj")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BconstructorX5Dstream_obj() -> *mut u8 { + unsafe { $($path_to_types)*::_export_constructor_stream_obj_cabi::<<$ty as $($path_to_types)*::Guest>::StreamObj>() } + } + #[cfg_attr(target_arch = "wasm32", export_name = "[method]stream-obj.clone")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eclone(arg0: *mut u8,) -> *mut u8 { + unsafe { $($path_to_types)*::_export_method_stream_obj_clone_cabi::<<$ty as $($path_to_types)*::Guest>::StreamObj>(arg0) } + } + #[cfg_attr(target_arch = "wasm32", export_name = "[method]stream-obj.is-write-closed")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eis_write_closed(arg0: *mut u8,) -> i32 { + unsafe { $($path_to_types)*::_export_method_stream_obj_is_write_closed_cabi::<<$ty as $($path_to_types)*::Guest>::StreamObj>(arg0) } + } + #[cfg_attr(target_arch = "wasm32", export_name = "[method]stream-obj.start-reading")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Estart_reading(arg0: *mut u8,arg1: *mut u8,) { + unsafe { $($path_to_types)*::_export_method_stream_obj_start_reading_cabi::<<$ty as $($path_to_types)*::Guest>::StreamObj>(arg0, arg1) } + } + #[cfg_attr(target_arch = "wasm32", export_name = "[method]stream-obj.write-ready-activate")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Ewrite_ready_activate(arg0: *mut u8,) { + unsafe { $($path_to_types)*::_export_method_stream_obj_write_ready_activate_cabi::<<$ty as $($path_to_types)*::Guest>::StreamObj>(arg0) } + } + #[cfg_attr(target_arch = "wasm32", export_name = "[method]stream-obj.read-ready-subscribe")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eread_ready_subscribe(arg0: *mut u8,) -> *mut u8 { + unsafe { $($path_to_types)*::_export_method_stream_obj_read_ready_subscribe_cabi::<<$ty as $($path_to_types)*::Guest>::StreamObj>(arg0) } + } + #[cfg_attr(target_arch = "wasm32", export_name = "[method]stream-obj.read-result")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eread_result(arg0: *mut u8,arg1: *mut u8,) { + unsafe { $($path_to_types)*::_export_method_stream_obj_read_result_cabi::<<$ty as $($path_to_types)*::Guest>::StreamObj>(arg0, arg1) } + } + #[cfg_attr(target_arch = "wasm32", export_name = "[method]stream-obj.is-ready-to-write")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eis_ready_to_write(arg0: *mut u8,) -> i32 { + unsafe { $($path_to_types)*::_export_method_stream_obj_is_ready_to_write_cabi::<<$ty as $($path_to_types)*::Guest>::StreamObj>(arg0) } + } + #[cfg_attr(target_arch = "wasm32", export_name = "[method]stream-obj.write-ready-subscribe")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Ewrite_ready_subscribe(arg0: *mut u8,) -> *mut u8 { + unsafe { $($path_to_types)*::_export_method_stream_obj_write_ready_subscribe_cabi::<<$ty as $($path_to_types)*::Guest>::StreamObj>(arg0) } + } + #[cfg_attr(target_arch = "wasm32", export_name = "[method]stream-obj.start-writing")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Estart_writing(arg0: *mut u8,) -> *mut u8 { + unsafe { $($path_to_types)*::_export_method_stream_obj_start_writing_cabi::<<$ty as $($path_to_types)*::Guest>::StreamObj>(arg0) } + } + #[cfg_attr(target_arch = "wasm32", export_name = "[method]stream-obj.finish-writing")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Efinish_writing(arg0: *mut u8,arg1: i32,arg2: *mut u8,) { + unsafe { $($path_to_types)*::_export_method_stream_obj_finish_writing_cabi::<<$ty as $($path_to_types)*::Guest>::StreamObj>(arg0, arg1, arg2) } + } + #[cfg_attr(target_arch = "wasm32", export_name = "[method]stream-obj.read-ready-activate")] + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5BmethodX5Dstream_objX2Eread_ready_activate(arg0: *mut u8,) { + unsafe { $($path_to_types)*::_export_method_stream_obj_read_ready_activate_cabi::<<$ty as $($path_to_types)*::Guest>::StreamObj>(arg0) } + } + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5Bresource_dropX5Daddress(arg0: usize) { + $($path_to_types)*::_export_drop_address_cabi::<<$ty as $($path_to_types)*::Guest>::Address>(arg0) + } + + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5Bresource_dropX5Dbuffer(arg0: usize) { + $($path_to_types)*::_export_drop_buffer_cabi::<<$ty as $($path_to_types)*::Guest>::Buffer>(arg0) + } + + #[cfg_attr(not(target_arch = "wasm32"), no_mangle)] + unsafe extern "C" fn symmetricX3AruntimeX2Fsymmetric_streamX400X2E2X2E1X00X5Bresource_dropX5Dstream_obj(arg0: usize) { + $($path_to_types)*::_export_drop_streamObj_cabi::<<$ty as $($path_to_types)*::Guest>::StreamObj>(arg0) + } + + };); +} + #[doc(hidden)] + pub(crate) use __export_symmetric_runtime_symmetric_stream_0_2_1_cabi; + } + } + } +} +mod _rt { + #![allow(dead_code, clippy::all)] + + use core::fmt; + use core::marker; + use core::sync::atomic::{AtomicUsize, Ordering::Relaxed}; + + /// A type which represents a component model resource, either imported or + /// exported into this component. + /// + /// This is a low-level wrapper which handles the lifetime of the resource + /// (namely this has a destructor). The `T` provided defines the component model + /// intrinsics that this wrapper uses. + /// + /// One of the chief purposes of this type is to provide `Deref` implementations + /// to access the underlying data when it is owned. + /// + /// This type is primarily used in generated code for exported and imported + /// resources. + #[repr(transparent)] + pub struct Resource { + // NB: This would ideally be `usize` but it is not. The fact that this has + // interior mutability is not exposed in the API of this type except for the + // `take_handle` method which is supposed to in theory be private. + // + // This represents, almost all the time, a valid handle value. When it's + // invalid it's stored as `0`. + handle: AtomicUsize, + _marker: marker::PhantomData, + } + + /// A trait which all wasm resources implement, namely providing the ability to + /// drop a resource. + /// + /// This generally is implemented by generated code, not user-facing code. + #[allow(clippy::missing_safety_doc)] + pub unsafe trait WasmResource { + /// Invokes the `[resource-drop]...` intrinsic. + unsafe fn drop(handle: usize); + } + + impl Resource { + #[doc(hidden)] + pub unsafe fn from_handle(handle: usize) -> Self { + debug_assert!(handle != 0); + Self { + handle: AtomicUsize::new(handle), + _marker: marker::PhantomData, + } + } + + /// Takes ownership of the handle owned by `resource`. + /// + /// Note that this ideally would be `into_handle` taking `Resource` by + /// ownership. The code generator does not enable that in all situations, + /// unfortunately, so this is provided instead. + /// + /// Also note that `take_handle` is in theory only ever called on values + /// owned by a generated function. For example a generated function might + /// take `Resource` as an argument but then call `take_handle` on a + /// reference to that argument. In that sense the dynamic nature of + /// `take_handle` should only be exposed internally to generated code, not + /// to user code. + #[doc(hidden)] + pub fn take_handle(resource: &Resource) -> usize { + resource.handle.swap(0, Relaxed) + } + + #[doc(hidden)] + pub fn handle(resource: &Resource) -> usize { + resource.handle.load(Relaxed) + } + } + + impl fmt::Debug for Resource { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Resource") + .field("handle", &self.handle) + .finish() + } + } + + impl Drop for Resource { + fn drop(&mut self) { + unsafe { + match self.handle.load(Relaxed) { + // If this handle was "taken" then don't do anything in the + // destructor. + 0 => {} + + // ... but otherwise do actually destroy it with the imported + // component model intrinsic as defined through `T`. + other => T::drop(other), + } + } + } + } + pub unsafe fn bool_lift(val: u8) -> bool { + if cfg!(debug_assertions) { + match val { + 0 => false, + 1 => true, + _ => panic!("invalid bool discriminant"), + } + } else { + val != 0 + } + } + + pub fn as_i64(t: T) -> i64 { + t.as_i64() + } + + pub trait AsI64 { + fn as_i64(self) -> i64; + } + + impl<'a, T: Copy + AsI64> AsI64 for &'a T { + fn as_i64(self) -> i64 { + (*self).as_i64() + } + } + + impl AsI64 for i64 { + #[inline] + fn as_i64(self) -> i64 { + self as i64 + } + } + + impl AsI64 for u64 { + #[inline] + fn as_i64(self) -> i64 { + self as i64 + } + } + pub use alloc_crate::boxed::Box; + + #[cfg(target_arch = "wasm32")] + pub fn run_ctors_once() { + wit_bindgen::rt::run_ctors_once(); + } + pub unsafe fn invalid_enum_discriminant() -> T { + if cfg!(debug_assertions) { + panic!("invalid enum discriminant") + } else { + unsafe { core::hint::unreachable_unchecked() } + } + } + extern crate alloc as alloc_crate; +} + +/// Generates `#[unsafe(no_mangle)]` functions to export the specified type as +/// the root implementation of all generated traits. +/// +/// For more information see the documentation of `wit_bindgen::generate!`. +/// +/// ```rust +/// # macro_rules! export{ ($($t:tt)*) => (); } +/// # trait Guest {} +/// struct MyType; +/// +/// impl Guest for MyType { +/// // ... +/// } +/// +/// export!(MyType); +/// ``` +#[allow(unused_macros)] +#[doc(hidden)] + +macro_rules! __export_stream_impl_impl { + ($ty:ident) => (self::export!($ty with_types_in self);); + ($ty:ident with_types_in $($path_to_types_root:tt)*) => ( + $($path_to_types_root)*::exports::symmetric::runtime::symmetric_stream::__export_symmetric_runtime_symmetric_stream_0_2_1_cabi!($ty with_types_in $($path_to_types_root)*::exports::symmetric::runtime::symmetric_stream); + ) +} +#[doc(inline)] +pub(crate) use __export_stream_impl_impl as export; + +#[cfg(target_arch = "wasm32")] +#[unsafe(link_section = "component-type:wit-bindgen:0.41.0:symmetric:runtime@0.2.1:stream-impl:encoded world")] +#[doc(hidden)] +#[allow(clippy::octal_escapes)] +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 1806] = *b"\ +\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\x8c\x0d\x01A\x02\x01\ +A\x05\x01B$\x01m\x02\x07pending\x05ready\x04\0\x0ecallback-state\x03\0\0\x04\0\x11\ +callback-function\x03\x01\x04\0\x0dcallback-data\x03\x01\x04\0\x12event-subscrip\ +tion\x03\x01\x04\0\x0fevent-generator\x03\x01\x04\0\x15callback-registration\x03\ +\x01\x01m\x02\x07started\x0bnot-started\x04\0\x0bcall-status\x03\0\x07\x01h\x04\x01\ +@\x01\x04self\x09\0\x7f\x04\0\x20[method]event-subscription.ready\x01\x0a\x01i\x04\ +\x01@\x01\x0bnanosecondsw\0\x0b\x04\0'[static]event-subscription.from-timeout\x01\ +\x0c\x01@\x01\x04self\x09\0\x0b\x04\0\x1e[method]event-subscription.dup\x01\x0d\x01\ +@\x01\x04self\x09\x01\0\x04\0\x20[method]event-subscription.reset\x01\x0e\x01i\x05\ +\x01@\0\0\x0f\x04\0\x1c[constructor]event-generator\x01\x10\x01h\x05\x01@\x01\x04\ +self\x11\0\x0b\x04\0![method]event-generator.subscribe\x01\x12\x01@\x01\x04self\x11\ +\x01\0\x04\0\x20[method]event-generator.activate\x01\x13\x01i\x06\x01i\x03\x01@\x01\ +\x03obj\x14\0\x15\x04\0$[static]callback-registration.cancel\x01\x16\x01@\0\x01\0\ +\x04\0\x03run\x01\x17\x01i\x02\x01@\x03\x07trigger\x0b\x08callback\x18\x04data\x15\ +\0\x14\x04\0\x08register\x01\x19\x03\0*symmetric:runtime/symmetric-executor@0.2.\ +1\x05\0\x02\x03\0\0\x12event-subscription\x01B*\x02\x03\x02\x01\x01\x04\0\x12eve\ +nt-subscription\x03\0\0\x04\0\x07address\x03\x01\x04\0\x06buffer\x03\x01\x04\0\x0a\ +stream-obj\x03\x01\x01i\x02\x01i\x03\x01@\x02\x04addr\x05\x08capacityw\0\x06\x04\ +\0\x13[constructor]buffer\x01\x07\x01h\x03\x01@\x01\x04self\x08\0\x05\x04\0\x1a[\ +method]buffer.get-address\x01\x09\x01@\x01\x04self\x08\0w\x04\0\x17[method]buffe\ +r.get-size\x01\x0a\x01@\x02\x04self\x08\x04sizew\x01\0\x04\0\x17[method]buffer.s\ +et-size\x01\x0b\x04\0\x17[method]buffer.capacity\x01\x0a\x01i\x04\x01@\0\0\x0c\x04\ +\0\x17[constructor]stream-obj\x01\x0d\x01h\x04\x01@\x01\x04self\x0e\0\x0c\x04\0\x18\ +[method]stream-obj.clone\x01\x0f\x01@\x01\x04self\x0e\0\x7f\x04\0\"[method]strea\ +m-obj.is-write-closed\x01\x10\x01@\x02\x04self\x0e\x06buffer\x06\x01\0\x04\0\x20\ +[method]stream-obj.start-reading\x01\x11\x01@\x01\x04self\x0e\x01\0\x04\0'[metho\ +d]stream-obj.write-ready-activate\x01\x12\x01i\x01\x01@\x01\x04self\x0e\0\x13\x04\ +\0'[method]stream-obj.read-ready-subscribe\x01\x14\x01k\x06\x01@\x01\x04self\x0e\ +\0\x15\x04\0\x1e[method]stream-obj.read-result\x01\x16\x04\0$[method]stream-obj.\ +is-ready-to-write\x01\x10\x04\0([method]stream-obj.write-ready-subscribe\x01\x14\ +\x01@\x01\x04self\x0e\0\x06\x04\0\x20[method]stream-obj.start-writing\x01\x17\x01\ +@\x02\x04self\x0e\x06buffer\x15\x01\0\x04\0![method]stream-obj.finish-writing\x01\ +\x18\x04\0&[method]stream-obj.read-ready-activate\x01\x12\x04\0(symmetric:runtim\ +e/symmetric-stream@0.2.1\x05\x02\x04\0#symmetric:runtime/stream-impl@0.2.1\x04\0\ +\x0b\x11\x01\0\x0bstream-impl\x03\0\0\0G\x09producers\x01\x0cprocessed-by\x02\x0d\ +wit-component\x070.228.0\x10wit-bindgen-rust\x060.41.0"; + +#[inline(never)] +#[doc(hidden)] +pub fn __link_custom_section_describing_imports() { + wit_bindgen::rt::maybe_link_cabi_realloc(); +} diff --git a/crates/symmetric_executor/wit/executor.wit b/crates/symmetric_executor/wit/executor.wit new file mode 100644 index 000000000..7af955a8e --- /dev/null +++ b/crates/symmetric_executor/wit/executor.wit @@ -0,0 +1,100 @@ +// This interface will only work with symmetric ABI (shared everything), +// it can't be composed with the canonical ABI + +/// Asynchronous executor functionality for symmetric ABI +interface symmetric-executor { + // These pseudo-resources are just used to + // pass pointers to register + + /// Return value of an event callback + enum callback-state { + /// Call the function again + pending, + /// The function has completed, all results are written, data is freed, + /// calling the function again is not permitted as data became invalid! + ready, + } + + /// This wraps a user provided function of type + /// `fn (callback-data) -> callback-state` + resource callback-function; + /// This wraps opaque user data, freed by the callback when + /// it returns ready + resource callback-data; + + /// The receiving side of an event + resource event-subscription { + /// Whether the event is active (used by poll implementation) + ready: func() -> bool; + /// Create a timeout event + from-timeout: static func(nanoseconds: u64) -> event-subscription; + /// Duplicate the subscription (e.g. for repeated callback registering, same cost as subscribe) + dup: func() -> event-subscription; + /// Reset subscription to be inactive, only next trigger will ready it + reset: func(); + } + /// A user controlled event + resource event-generator { + constructor(); + /// Get the receiving side (to pass to other parts of the program) + subscribe: func() -> event-subscription; + /// Trigger all subscribers + activate: func(); + } + + /// Handle to cancel a callback registration + resource callback-registration { + /// returns the data passed to the registration + cancel: static func(obj: callback-registration) -> callback-data; + } + + /// Return value of an async call, lowest bit encoding + enum call-status { + /// For symmetric this means that processing has started, parameters should still remain valid until null, + /// params-read = non-null, results-written,done = null + started, + /// For symmetric: Retry the call (temporarily out of memory) + not-started, + } + + /// Wait until all registered events have completed + run: func(); + /// Register a callback for an event + register: func(trigger: event-subscription, callback: callback-function, data: callback-data) -> callback-registration; +} + +// language neutral stream implementation +interface symmetric-stream { + use symmetric-executor.{event-subscription}; + + resource address; + // special zero allocation/copy data type (caller provided buffer) + resource buffer { + constructor(addr: address, capacity: u64); + get-address: func () -> address; + get-size: func() -> u64; + set-size: func(size: u64); + capacity: func() -> u64; + } + + resource stream-obj { + constructor(); + // create a new instance e.g. for reading or tasks + clone: func() -> stream-obj; + // reading (in roughly chronological order) + /// indicates EOF + is-write-closed: func() -> bool; + start-reading: func(buffer: buffer); + write-ready-activate: func(); + read-ready-subscribe: func() -> event-subscription; + /// none is EOF when read-ready, no data when polled + read-result: func() -> option; + // writing + is-ready-to-write: func() -> bool; + write-ready-subscribe: func() -> event-subscription; + start-writing: func() -> buffer; + /// none is EOF + finish-writing: func(buffer: option); + read-ready-activate: func(); + } +} diff --git a/crates/symmetric_executor/wit/world.wit b/crates/symmetric_executor/wit/world.wit new file mode 100644 index 000000000..9bee0e5db --- /dev/null +++ b/crates/symmetric_executor/wit/world.wit @@ -0,0 +1,14 @@ +package symmetric:runtime@0.2.1; + +world executor { + export symmetric-executor; +} + +world stream-impl { + export symmetric-stream; +} + +world module { + import symmetric-executor; + import symmetric-stream; +} diff --git a/crates/test-rust-wasm/src/bin/strings.rs b/crates/test-rust-wasm/src/bin/strings.rs new file mode 100644 index 000000000..78305fdce --- /dev/null +++ b/crates/test-rust-wasm/src/bin/strings.rs @@ -0,0 +1,3 @@ +include!("../../../../tests/runtime/strings/wasm.rs"); + +fn main() {} diff --git a/crates/test/src/c.rs b/crates/test/src/c.rs index 8ffb09cf5..c775615db 100644 --- a/crates/test/src/c.rs +++ b/crates/test/src/c.rs @@ -74,7 +74,7 @@ impl LanguageMethods for C { ] } - fn prepare(&self, runner: &mut Runner<'_>) -> Result<()> { + fn prepare(&self, runner: &mut Runner<'_>, _: &str) -> Result<()> { prepare(runner, clang(runner)) } @@ -109,7 +109,7 @@ impl LanguageMethods for Cpp { C.should_fail_verify(name, config, args) } - fn prepare(&self, runner: &mut Runner<'_>) -> Result<()> { + fn prepare(&self, runner: &mut Runner<'_>, _: &str) -> Result<()> { prepare(runner, clangpp(runner)) } diff --git a/crates/test/src/cpp.rs b/crates/test/src/cpp.rs index 917d4da41..6dad80498 100644 --- a/crates/test/src/cpp.rs +++ b/crates/test/src/cpp.rs @@ -10,6 +10,10 @@ use std::process::Command; pub struct Cpp17; +pub struct State { + native_deps: Vec, +} + /// C/C++-specific configuration of component files #[derive(Default, Deserialize)] #[serde(deny_unknown_fields)] @@ -20,9 +24,13 @@ struct LangConfig { } fn clangpp(runner: &Runner<'_>) -> PathBuf { - match &runner.opts.c.wasi_sdk_path { - Some(path) => path.join("bin/wasm32-wasip2-clang++"), - None => "wasm32-wasip2-clang++".into(), + if runner.is_symmetric() { + "clang++".into() + } else { + match &runner.opts.c.wasi_sdk_path { + Some(path) => path.join("bin/wasm32-wasip2-clang++"), + None => "wasm32-wasip2-clang++".into(), + } } } @@ -48,7 +56,7 @@ impl LanguageMethods for Cpp17 { false } - fn prepare(&self, runner: &mut crate::Runner<'_>) -> anyhow::Result<()> { + fn prepare(&self, runner: &mut crate::Runner<'_>, test_name: &str) -> anyhow::Result<()> { let compiler = clangpp(runner); let cwd = std::env::current_dir()?; let dir = cwd.join(&runner.opts.artifacts).join("cpp"); @@ -65,6 +73,20 @@ impl LanguageMethods for Cpp17 { ); })?; + let mut native_deps = Vec::new(); + if runner.is_symmetric() { + let cwd = std::env::current_dir()?; + let dir = cwd.join(&runner.opts.artifacts).join("rust"); + let wit_bindgen = dir.join("wit-bindgen"); + let mut target_out_dir = wit_bindgen.join("target"); + target_out_dir.push("debug"); + + native_deps.push(target_out_dir); + let root_dir = runner.opts.artifacts.join(test_name); + native_deps.push(root_dir); + } + + runner.cpp_state = Some(State { native_deps }); Ok(()) } @@ -107,10 +129,15 @@ impl LanguageMethods for Cpp17 { helper_dir.push("cpp"); helper_dir.push("helper-types"); // for expected - let mut helper_dir2 = cwd; + let mut helper_dir2 = cwd.clone(); helper_dir2.push("crates"); helper_dir2.push("cpp"); helper_dir2.push("test_headers"); + // for async_support.h + let mut helper_dir3 = cwd.clone(); + helper_dir3.push("crates"); + helper_dir3.push("symmetric_executor"); + helper_dir3.push("cpp-client"); // Compile the C-based bindings to an object file. let bindings_object = compile.output.with_extension("bindings.o"); @@ -125,36 +152,46 @@ impl LanguageMethods for Cpp17 { .arg("-I") .arg(helper_dir.to_str().unwrap().to_string()) .arg("-I") - .arg(helper_dir2.to_str().unwrap().to_string()) - .arg("-fno-exceptions") - .arg("-Wall") - .arg("-Wextra") - .arg("-Werror") - .arg("-Wno-unused-parameter") - .arg("-std=c++17") - .arg("-c") - .arg("-g") - .arg("-o") - .arg(&bindings_object); + .arg(helper_dir2.to_str().unwrap().to_string()); + if runner.is_symmetric() { + cmd.arg("-I") + .arg(helper_dir3.to_str().unwrap().to_string()) + .arg("-fPIC"); + } + cmd.arg("-fno-exceptions") + .arg("-Wall") + .arg("-Wextra") + .arg("-Werror") + .arg("-Wno-unused-parameter") + .arg("-std=c++17") + .arg("-c") + .arg("-g") + .arg("-o") + .arg(&bindings_object); runner.run_command(&mut cmd)?; // Now compile the runner's source code to with the above object and the // component-type object into a final component. let mut cmd = Command::new(compiler); - cmd.arg(&compile.component.path) - .arg(&bindings_object) - .arg(compile.bindings_dir.join(format!( + cmd.arg(&compile.component.path).arg(&bindings_object); + if !runner.is_symmetric() { + cmd.arg(compile.bindings_dir.join(format!( "{}_component_type.o", compile.component.bindgen.world - ))) - .arg("-I") + ))); + } + cmd.arg("-I") .arg(&compile.bindings_dir) .arg("-I") .arg(helper_dir.to_str().unwrap().to_string()) .arg("-I") - .arg(helper_dir2.to_str().unwrap().to_string()) - .arg("-fno-exceptions") - .arg("-Wall") + .arg(helper_dir2.to_str().unwrap().to_string()); + if !runner.is_symmetric() { + cmd.arg("-fno-exceptions"); + } else { + cmd.arg("-I").arg(helper_dir3.to_str().unwrap().to_string()); + } + cmd.arg("-Wall") .arg("-Wextra") .arg("-Werror") .arg("-Wc++-compat") @@ -169,9 +206,34 @@ impl LanguageMethods for Cpp17 { match compile.component.kind { Kind::Runner => {} Kind::Test => { - cmd.arg("-mexec-model=reactor"); + if !runner.is_symmetric() { + cmd.arg("-mexec-model=reactor"); + } } } + if runner.is_symmetric() { + cmd.arg("-fPIC").arg(format!( + "-Wl,--version-script={}", + compile + .bindings_dir + .join(format!("{}.verscr", compile.component.bindgen.world)) + .to_str() + .unwrap() // .to_string(), + )); + for i in runner.cpp_state.as_ref().unwrap().native_deps.iter() { + cmd.arg(format!("-L{}", i.as_os_str().to_str().unwrap())); + } + if !matches!(compile.component.kind, Kind::Runner) { + cmd.arg("-shared"); + } else { + cmd.arg("-ltest-cpp17"); + } + cmd.arg("-L") + .arg(helper_dir3.to_str().unwrap().to_string()) + .arg("-lruntime") + .arg("-lsymmetric_stream") + .arg("-lsymmetric_executor"); + } runner.run_command(&mut cmd)?; Ok(()) } @@ -196,4 +258,8 @@ impl LanguageMethods for Cpp17 { .arg(verify.artifacts_dir.join("tmp.o")); runner.run_command(&mut cmd) } + + fn default_bindgen_args(&self) -> &[&str] { + &["--format"] + } } diff --git a/crates/test/src/csharp.rs b/crates/test/src/csharp.rs index 0bfeebbbc..4d3d36db2 100644 --- a/crates/test/src/csharp.rs +++ b/crates/test/src/csharp.rs @@ -43,7 +43,7 @@ impl LanguageMethods for Csharp { config.async_ } - fn prepare(&self, runner: &mut Runner<'_>) -> Result<()> { + fn prepare(&self, runner: &mut Runner<'_>, _: &str) -> Result<()> { runner.run_command(dotnet().arg("--version"))?; Ok(()) diff --git a/crates/test/src/custom.rs b/crates/test/src/custom.rs index c260d8640..01ed96ef8 100644 --- a/crates/test/src/custom.rs +++ b/crates/test/src/custom.rs @@ -117,7 +117,7 @@ impl LanguageMethods for Language { ) } - fn prepare(&self, runner: &mut Runner<'_>) -> Result<()> { + fn prepare(&self, runner: &mut Runner<'_>, _: &str) -> Result<()> { let dir = env::current_dir()? .join(&runner.opts.artifacts) .join(&self.extension); diff --git a/crates/test/src/lib.rs b/crates/test/src/lib.rs index c41a35004..e3541e419 100644 --- a/crates/test/src/lib.rs +++ b/crates/test/src/lib.rs @@ -6,6 +6,7 @@ use std::collections::{HashMap, HashSet}; use std::fmt; use std::fs; use std::io::Write; +use std::os::unix::fs::symlink; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::sync::Arc; @@ -104,6 +105,10 @@ pub struct Opts { /// Passing `--lang rust` will only test Rust for example. #[clap(short, long, required = true, value_delimiter = ',')] languages: Vec, + + /// Generate code for symmetric ABI and compile to native + #[clap(short, long)] + symmetric: bool, } impl Opts { @@ -111,6 +116,7 @@ impl Opts { Runner { opts: self, rust_state: None, + cpp_state: None, wit_bindgen, test_runner: runner::TestRunner::new(&self.runner)?, } @@ -222,6 +228,7 @@ struct Verify<'a> { struct Runner<'a> { opts: &'a Opts, rust_state: Option, + cpp_state: Option, wit_bindgen: &'a Path, test_runner: runner::TestRunner, } @@ -440,6 +447,23 @@ impl Runner<'_> { }); } + let has_link_name = bindgen + .args + .iter() + .any(|elem| elem.starts_with("--link-name")); + if self.is_symmetric() && matches!(kind, Kind::Runner) && !has_link_name { + match &language { + Language::Rust => { + bindgen.args.push(String::from("--link-name")); + bindgen.args.push(String::from("test-rust")); + } + _ => { + println!("Symmetric: --link_name missing from language {language:?}"); + // todo!(); + } + } + } + Ok(Component { name: path.file_stem().unwrap().to_str().unwrap().to_string(), path: path.to_path_buf(), @@ -457,12 +481,12 @@ impl Runner<'_> { let all_languages = self.all_languages(); let mut prepared = HashSet::new(); - let mut prepare = |lang: &Language| -> Result<()> { + let mut prepare = |lang: &Language, name: &str| -> Result<()> { if !self.include_language(lang) || !prepared.insert(lang.clone()) { return Ok(()); } lang.obj() - .prepare(self) + .prepare(self, name) .with_context(|| format!("failed to prepare language {lang}")) }; @@ -470,12 +494,12 @@ impl Runner<'_> { match &test.kind { TestKind::Runtime(c) => { for component in c { - prepare(&component.language)? + prepare(&component.language, &test.name)? } } TestKind::Codegen(_) => { for lang in all_languages.iter() { - prepare(lang)?; + prepare(lang, &test.name)?; } } } @@ -525,6 +549,10 @@ impl Runner<'_> { args.push(arg.to_string()); } + if self.is_symmetric() { + args.push(String::from("--symmetric")) + } + codegen_tests.push(( language.clone(), test, @@ -662,7 +690,7 @@ impl Runner<'_> { // In parallel compile all sources to their binary component // form. let compile_results = components - .par_iter() + .iter() .map(|(test, component)| { let path = self .compile_component(test, component) @@ -807,7 +835,14 @@ impl Runner<'_> { let artifacts_dir = root_dir.join(format!("{}-{}", component.name, component.language)); let _ = fs::remove_dir_all(&artifacts_dir); let bindings_dir = artifacts_dir.join("bindings"); - let output = root_dir.join(format!("{}-{}.wasm", component.name, component.language)); + let output = root_dir.join(if self.is_symmetric() { + match &component.kind { + Kind::Runner => format!("{}-{}_exe", component.name, component.language), + Kind::Test => format!("lib{}-{}.so", component.name, component.language), + } + } else { + format!("{}-{}.wasm", component.name, component.language) + }); component .language .obj() @@ -820,15 +855,17 @@ impl Runner<'_> { }; component.language.obj().compile(self, &result)?; - // Double-check the output is indeed a component and it's indeed valid. - let wasm = fs::read(&output) - .with_context(|| format!("failed to read output wasm file {output:?}"))?; - if !wasmparser::Parser::is_component(&wasm) { - bail!("output file {output:?} is not a component"); + if !self.is_symmetric() { + // Double-check the output is indeed a component and it's indeed valid. + let wasm = fs::read(&output) + .with_context(|| format!("failed to read output wasm file {output:?}"))?; + if !wasmparser::Parser::is_component(&wasm) { + bail!("output file {output:?} is not a component"); + } + wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all()) + .validate_all(&wasm) + .with_context(|| format!("compiler produced invalid wasm file {output:?}"))?; } - wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all()) - .validate_all(&wasm) - .with_context(|| format!("compiler produced invalid wasm file {output:?}"))?; Ok(output) } @@ -849,7 +886,9 @@ impl Runner<'_> { // done for async tests at this time to ensure that there's a version of // composition that's done which is at the same version as wasmparser // and friends. - let composed = if case.config.wac.is_none() && test_components.len() == 1 { + let composed = if self.is_symmetric() { + Vec::new() + } else if case.config.wac.is_none() && test_components.len() == 1 { self.compose_wasm_with_wasm_compose(runner_wasm, test_components)? } else { self.compose_wasm_with_wac(case, runner, runner_wasm, test_components)? @@ -864,11 +903,46 @@ impl Runner<'_> { filename.push_str("-"); filename.push_str(test.path.file_name().unwrap().to_str().unwrap()); } - filename.push_str(".wasm"); + if !self.is_symmetric() { + filename.push_str(".wasm"); + } let composed_wasm = dst.join(filename); - write_if_different(&composed_wasm, &composed)?; + if !self.is_symmetric() { + write_if_different(&composed_wasm, &composed)?; - self.run_command(self.test_runner.command().arg(&composed_wasm))?; + self.run_command(self.test_runner.command().arg(&composed_wasm))?; + } else { + if std::fs::exists(composed_wasm.clone())? { + std::fs::remove_dir_all(composed_wasm.clone())?; + } + std::fs::create_dir(composed_wasm.clone())?; + + let mut new_file = composed_wasm.clone(); + new_file.push(&(runner_wasm.file_name().unwrap())); + symlink(runner_wasm, new_file)?; + for (_c, p) in test_components.iter() { + let mut new_file = composed_wasm.clone(); + new_file.push(&(p.file_name().unwrap())); + symlink(p, new_file)?; + } + let cwd = runner_wasm.parent().unwrap().parent().unwrap(); + let dir = cwd.join("rust"); + let wit_bindgen = dir.join("wit-bindgen"); + let so_dir = wit_bindgen.join("target").join("debug").join("deps"); + symlink( + so_dir.join("libsymmetric_executor.so"), + composed_wasm.join("libsymmetric_executor.so"), + )?; + symlink( + so_dir.join("libsymmetric_stream.so"), + composed_wasm.join("libsymmetric_stream.so"), + )?; + + let mut cmd = Command::new(runner_wasm); + cmd.env("LD_LIBRARY_PATH", "."); + cmd.current_dir(composed_wasm); + self.run_command(&mut cmd)?; + } Ok(()) } @@ -973,6 +1047,9 @@ impl Runner<'_> { .output() .with_context(|| format!("failed to spawn {cmd:?}"))?; if output.status.success() { + if std::env::var("WIT_BINDGEN_TRACE").is_ok() { + eprintln!("$ {cmd:?}"); + } return Ok(()); } @@ -1081,6 +1158,10 @@ status: {}", std::process::exit(1); } } + + fn is_symmetric(&self) -> bool { + self.opts.symmetric + } } struct StepResult<'a> { @@ -1138,7 +1219,7 @@ trait LanguageMethods { /// Performs any one-time preparation necessary for this language, such as /// downloading or caching dependencies. - fn prepare(&self, runner: &mut Runner<'_>) -> Result<()>; + fn prepare(&self, runner: &mut Runner<'_>, name: &str) -> Result<()>; /// Add some files to the generated directory _before_ calling bindgen fn generate_bindings_prepare( @@ -1166,6 +1247,9 @@ trait LanguageMethods { .arg(format!("%{}", bindgen.world)) .arg("--out-dir") .arg(dir); + if runner.is_symmetric() { + cmd.arg("--symmetric"); + } match bindgen.wit_config.default_bindgen_args { Some(true) | None => { diff --git a/crates/test/src/moonbit.rs b/crates/test/src/moonbit.rs index 5b3dd0fa7..61d1d7846 100644 --- a/crates/test/src/moonbit.rs +++ b/crates/test/src/moonbit.rs @@ -26,7 +26,7 @@ impl LanguageMethods for MoonBit { &["--derive-show", "--derive-eq", "--derive-error"] } - fn prepare(&self, runner: &mut crate::Runner<'_>) -> anyhow::Result<()> { + fn prepare(&self, runner: &mut crate::Runner<'_>, _: &str) -> anyhow::Result<()> { println!("Testing if MoonBit toolchain exists..."); if runner .run_command(Command::new("moon").arg("version")) diff --git a/crates/test/src/rust.rs b/crates/test/src/rust.rs index 9f68f7593..2465b6854 100644 --- a/crates/test/src/rust.rs +++ b/crates/test/src/rust.rs @@ -30,6 +30,7 @@ pub struct State { wit_bindgen_rlib: PathBuf, futures_rlib: PathBuf, wit_bindgen_deps: Vec, + native_deps: Vec, } /// Rust-specific configuration of component files @@ -94,20 +95,34 @@ impl LanguageMethods for Rust { &["--stubs"] } - fn prepare(&self, runner: &mut Runner<'_>) -> Result<()> { + fn prepare(&self, runner: &mut Runner<'_>, test_name: &str) -> Result<()> { let cwd = env::current_dir()?; let opts = &runner.opts.rust; let dir = cwd.join(&runner.opts.artifacts).join("rust"); let wit_bindgen = dir.join("wit-bindgen"); - let wit_bindgen_dep = match &opts.rust_wit_bindgen_path { - Some(path) => format!("path = {:?}", cwd.join(path)), - None => { - let version = opts - .rust_wit_bindgen_version - .as_deref() - .unwrap_or(env!("CARGO_PKG_VERSION")); - format!("version = \"{version}\"") + let mut symmetric_runtime = String::new(); + let wit_bindgen_dep = if runner.is_symmetric() { + let bindgen_path = cwd.join("crates/symmetric_executor/dummy-bindgen"); + let executor_path = cwd.join("crates/symmetric_executor"); + symmetric_runtime.push_str(&format!( + "symmetric_executor = {{ path = {executor_path:?}, features = [\"trace\"] }}\n" + )); + symmetric_runtime.push_str(&format!( + "symmetric_stream = {{ path = \"{}/symmetric_stream\", features = [\"trace\"] }}\n", + executor_path.display() + )); + format!("path = {bindgen_path:?}, package = \"mini-bindgen\"") + } else { + match &opts.rust_wit_bindgen_path { + Some(path) => format!("path = {:?}", cwd.join(path)), + None => { + let version = opts + .rust_wit_bindgen_version + .as_deref() + .unwrap_or(env!("CARGO_PKG_VERSION")); + format!("version = \"{version}\"") + } } }; @@ -123,6 +138,7 @@ name = "tmp" [dependencies] wit-bindgen = {{ {wit_bindgen_dep} }} futures = "0.3.31" +{symmetric_runtime} [lib] path = 'lib.rs' @@ -132,30 +148,50 @@ path = 'lib.rs' super::write_if_different(&wit_bindgen.join("lib.rs"), "")?; println!("Building `wit-bindgen` from crates.io..."); - runner.run_command( - Command::new("cargo") - .current_dir(&wit_bindgen) - .arg("build") - .arg("-pwit-bindgen") - .arg("-pfutures") - .arg("--target") - .arg(&opts.rust_target), - )?; + let mut cmd = Command::new("cargo"); + cmd.current_dir(&wit_bindgen) + .arg("build") + .arg(if runner.is_symmetric() { + "-pmini-bindgen" + } else { + "-pwit-bindgen" + }) + .arg("-pfutures"); + if !runner.is_symmetric() { + cmd.arg("--target").arg(&opts.rust_target); + } else { + cmd.arg("-psymmetric_executor").arg("-psymmetric_stream"); + } + runner.run_command(&mut cmd)?; - let target_out_dir = wit_bindgen - .join("target") - .join(&opts.rust_target) - .join("debug"); + let mut target_out_dir = wit_bindgen.join("target"); + if !runner.is_symmetric() { + target_out_dir = target_out_dir.join(&opts.rust_target); + } + target_out_dir = target_out_dir.join("debug"); let host_out_dir = wit_bindgen.join("target/debug"); - let wit_bindgen_rlib = target_out_dir.join("libwit_bindgen.rlib"); + let wit_bindgen_rlib = target_out_dir.join(if runner.is_symmetric() { + "libmini_bindgen.rlib" + } else { + "libwit_bindgen.rlib" + }); let futures_rlib = target_out_dir.join("libfutures.rlib"); assert!(wit_bindgen_rlib.exists()); assert!(futures_rlib.exists()); + let wit_bindgen_deps = vec![target_out_dir.join("deps"), host_out_dir.join("deps")]; + let mut native_deps = Vec::new(); + if runner.is_symmetric() { + native_deps.push(target_out_dir); + let root_dir = runner.opts.artifacts.join(test_name); + native_deps.push(root_dir); + } + runner.rust_state = Some(State { wit_bindgen_rlib, futures_rlib, - wit_bindgen_deps: vec![target_out_dir.join("deps"), host_out_dir.join("deps")], + wit_bindgen_deps, + native_deps, }); Ok(()) } @@ -294,21 +330,28 @@ impl Runner<'_> { .arg(&format!( "--extern=futures={}", state.futures_rlib.display() - )) - .arg("--target") - .arg(&opts.rust_target) - .arg("-Dwarnings") - .arg("-Cdebuginfo=1"); + )); + if !self.is_symmetric() { + cmd.arg("--target").arg(&opts.rust_target); + } + cmd.arg("-Dwarnings").arg("-Cdebuginfo=1"); for dep in state.wit_bindgen_deps.iter() { cmd.arg(&format!("-Ldependency={}", dep.display())); } + for dep in state.native_deps.iter() { + cmd.arg(&format!("-Lnative={}", dep.display())); + } cmd } fn produces_component(&self) -> bool { - match self.opts.rust.rust_target.as_str() { - "wasm32-unknown-unknown" | "wasm32-wasi" | "wasm32-wasip1" => false, - _ => true, + if self.is_symmetric() { + true + } else { + match self.opts.rust.rust_target.as_str() { + "wasm32-unknown-unknown" | "wasm32-wasi" | "wasm32-wasip1" => false, + _ => true, + } } } } diff --git a/crates/test/src/wat.rs b/crates/test/src/wat.rs index 6209000c2..a6cec77bb 100644 --- a/crates/test/src/wat.rs +++ b/crates/test/src/wat.rs @@ -25,7 +25,7 @@ impl LanguageMethods for Wat { Some(";;@") } - fn prepare(&self, _runner: &mut Runner<'_>) -> Result<()> { + fn prepare(&self, _runner: &mut Runner<'_>, _: &str) -> Result<()> { Ok(()) } diff --git a/src/bin/wit-bindgen.rs b/src/bin/wit-bindgen.rs index 0c909439e..42ba1ad41 100644 --- a/src/bin/wit-bindgen.rs +++ b/src/bin/wit-bindgen.rs @@ -46,6 +46,14 @@ enum Opt { #[clap(flatten)] args: Common, }, + /// Generates bindings for bridge modules between wasm and native. + #[cfg(feature = "bridge")] + Bridge { + #[clap(flatten)] + opts: wit_bindgen_bridge::Opts, + #[clap(flatten)] + args: Common, + }, /// Generates bindings for C++ modules. #[cfg(feature = "cpp")] Cpp { @@ -136,6 +144,8 @@ fn main() -> Result<()> { Opt::Moonbit { opts, args } => (opts.build(), args), #[cfg(feature = "c")] Opt::C { opts, args } => (opts.build(), args), + #[cfg(feature = "bridge")] + Opt::Bridge { opts, args } => (opts.build(), args), #[cfg(feature = "cpp")] Opt::Cpp { opts, args } => (opts.build(args.out_dir.as_ref()), args), #[cfg(feature = "rust")] @@ -220,7 +230,8 @@ fn gen_world( } } let (pkg, _files) = resolve.push_path(&opts.wit)?; - let world = resolve.select_world(pkg, opts.world.as_deref())?; + let mut world = resolve.select_world(pkg, opts.world.as_deref())?; + generator.apply_resolve_options(&mut resolve, &mut world); generator.generate(&resolve, world, files)?; Ok(()) diff --git a/tests/autosar/ara.wit b/tests/autosar/ara.wit new file mode 100644 index 000000000..480159399 --- /dev/null +++ b/tests/autosar/ara.wit @@ -0,0 +1,99 @@ +package autosar:ara + +interface types { + type error-code-type = s32 + + resource error-domain { + name: func() -> string + message: func(n: error-code-type) -> string + // strictly no dtor, because of static lifetime + } + + record error-code { + value: error-code-type, + domain: s32, // borrow, + } +} + +interface e2exf { + enum configuration-format { json, xml } + + // status-handler-configure: func( + // binding-configuration: string, binding-format: configuration-format, + // e2exf-configuration: string, e2exf-format: configuration-format) -> bool +} + +interface core { + use types.{error-code} + resource instance-specifier { +// constructor(spec: string) + to-string: func() -> string + // needed due to SWS_CM_00118 (by value) + clone: func() -> instance-specifier + create: static func(spec: string) -> result + } + + initialize: func() -> result<_, error-code> + deinitialize: func() -> result<_, error-code> + + create-instance-specifier: func(spec: string) -> result +// instance-specifier: func() -> result +} + +interface log { + resource logger { + report: func(level: u32, message: string) + constructor(context: string, description: string, level: u32) + } + +// create: func(context: string, description: string, level: u32) -> logger-handle +} +interface com { + resource instance-identifier { + constructor(id: string) + to-string: func() -> string + } + + enum s-m-state { + valid, + no-data, + init, + invalid, + state-m-disabled, + } + + enum profile-check-status { + ok, + repeated, + wrong-sequence, + error, + not-available, + check-disabled, + } + + use e2exf.{configuration-format} + use core.{instance-specifier} + + // use e2exf.{status-handler-configure} (doesn't work for some reason) + status-handler-configure: func( + binding-configuration: string, binding-format: configuration-format, + e2exf-configuration: string, e2exf-format: configuration-format) -> bool + resolve-instance-ids: func(spec: borrow) -> list +} +interface exec { + enum execution-state { running } + + resource execution-client { + constructor() + report-execution-state: func(state: execution-state) + } + +// create-execution-client: func() -> execution-client +} + +world ara { + import core + import log + import com + import exec +} diff --git a/tests/autosar/deps.lock b/tests/autosar/deps.lock new file mode 100644 index 000000000..fab8231c5 --- /dev/null +++ b/tests/autosar/deps.lock @@ -0,0 +1,26 @@ +[cli] +url = "https://github.com/WebAssembly/wasi-cli/archive/main.tar.gz" +sha256 = "fb029d0f9468fcb404a079a58fafd9265ef99c0ee1350835348da7b6e105c597" +sha512 = "8602e881281adc67b1ac5a4eb0888636d6f50d15bd14e36dcc446a51551f3f9bb3e9eabb776d723bb113bf1e26a702c5042de095e66e897c3d3cf689e0b7d4f9" +deps = ["clocks", "filesystem", "random", "sockets"] + +[clocks] +sha256 = "89da8eca4cd195516574c89c5b3c24a7b5af3ff2565c16753d20d3bdbc5fc60f" +sha512 = "244079b3f592d58478a97adbd0bee8d49ae9dd1a3e435651ee40997b50da9fe62cfaba7e3ec7f7406d7d0288d278a43a3a0bc5150226ba40ce0f8ac6d33f7ddb" + +[filesystem] +sha256 = "05952bbc98895aa3aeda6c765a3e521016de59f993f3b60394c724640935c09c" +sha512 = "2c242489801a75466986fe014d730fb3aa7b5c6e56a230c8735e6672711b58bcbe92ba78a341b78fc3ac69339e55d3859d8bb14410230f0371ee30dbd83add64" + +[io] +url = "https://github.com/WebAssembly/wasi-io/archive/main.tar.gz" +sha256 = "b622db2755978a49d18d35d84d75f66b2b1ed23d7bf413e5c9e152e190cc7d4b" +sha512 = "d19c9004e75bf3ebe3e34cff498c3d7fee04cd57a7fba7ed12a0c5ad842ba5715c009de77a152c57da0500f6ca0986b6791b6f022829bdd5a024f7bc114c2ff6" + +[random] +sha256 = "11afcbff9920f5f1f72b6764d01e59a5faa2c671f0c59f0c9b405778f3708745" +sha512 = "cc4fa3d178559a89d9d6a376e3359b892158d1e73317c5db1f797ebc6b0b57abf2422797648d5b2b494c50cf9360720bc451cc27e15def7d278ba875805ccbf5" + +[sockets] +sha256 = "b5c2e9cc87cefbaef06bbe9978f9bc336da9feee2d51747bc28e10164fc46c39" +sha512 = "3aea6fe0c768b27d5c5cb3adab5e60dc936198f8b677c2cf6c4d57a0460db87eb779e0b577f1240fb2a6bf3ade49919fbffe39b0137bce3242343e6091cc7510" diff --git a/tests/autosar/deps.toml b/tests/autosar/deps.toml new file mode 100644 index 000000000..968b655bf --- /dev/null +++ b/tests/autosar/deps.toml @@ -0,0 +1,4 @@ +# Use `wit-deps update` to pull in latest changes from "dynamic" branch references +#poll = "https://github.com/WebAssembly/wasi-poll/archive/main.tar.gz" +io = "https://github.com/WebAssembly/wasi-io/archive/main.tar.gz" +cli = "https://github.com/WebAssembly/wasi-cli/archive/main.tar.gz" diff --git a/tests/autosar/deps/cli/command.wit b/tests/autosar/deps/cli/command.wit new file mode 100644 index 000000000..74811d327 --- /dev/null +++ b/tests/autosar/deps/cli/command.wit @@ -0,0 +1,7 @@ +package wasi:cli@0.2.0-rc-2023-11-10; + +world command { + include reactor; + + export run; +} diff --git a/tests/autosar/deps/cli/environment.wit b/tests/autosar/deps/cli/environment.wit new file mode 100644 index 000000000..70065233e --- /dev/null +++ b/tests/autosar/deps/cli/environment.wit @@ -0,0 +1,18 @@ +interface environment { + /// Get the POSIX-style environment variables. + /// + /// Each environment variable is provided as a pair of string variable names + /// and string value. + /// + /// Morally, these are a value import, but until value imports are available + /// in the component model, this import function should return the same + /// values each time it is called. + get-environment: func() -> list>; + + /// Get the POSIX-style arguments to the program. + get-arguments: func() -> list; + + /// Return a path that programs should use as their initial current working + /// directory, interpreting `.` as shorthand for this. + initial-cwd: func() -> option; +} diff --git a/tests/autosar/deps/cli/exit.wit b/tests/autosar/deps/cli/exit.wit new file mode 100644 index 000000000..d0c2b82ae --- /dev/null +++ b/tests/autosar/deps/cli/exit.wit @@ -0,0 +1,4 @@ +interface exit { + /// Exit the current instance and any linked instances. + exit: func(status: result); +} diff --git a/tests/autosar/deps/cli/reactor.wit b/tests/autosar/deps/cli/reactor.wit new file mode 100644 index 000000000..eafa2fd49 --- /dev/null +++ b/tests/autosar/deps/cli/reactor.wit @@ -0,0 +1,31 @@ +package wasi:cli@0.2.0-rc-2023-11-10; + +world reactor { + import wasi:clocks/wall-clock@0.2.0-rc-2023-11-10; + import wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-10; + import wasi:filesystem/types@0.2.0-rc-2023-11-10; + import wasi:filesystem/preopens@0.2.0-rc-2023-11-10; + import wasi:sockets/instance-network@0.2.0-rc-2023-11-10; + import wasi:sockets/ip-name-lookup@0.2.0-rc-2023-11-10; + import wasi:sockets/network@0.2.0-rc-2023-11-10; + import wasi:sockets/tcp-create-socket@0.2.0-rc-2023-11-10; + import wasi:sockets/tcp@0.2.0-rc-2023-11-10; + import wasi:sockets/udp-create-socket@0.2.0-rc-2023-11-10; + import wasi:sockets/udp@0.2.0-rc-2023-11-10; + import wasi:random/random@0.2.0-rc-2023-11-10; + import wasi:random/insecure@0.2.0-rc-2023-11-10; + import wasi:random/insecure-seed@0.2.0-rc-2023-11-10; + import wasi:io/poll@0.2.0-rc-2023-11-10; + import wasi:io/streams@0.2.0-rc-2023-11-10; + + import environment; + import exit; + import stdin; + import stdout; + import stderr; + import terminal-input; + import terminal-output; + import terminal-stdin; + import terminal-stdout; + import terminal-stderr; +} diff --git a/tests/autosar/deps/cli/run.wit b/tests/autosar/deps/cli/run.wit new file mode 100644 index 000000000..a70ee8c03 --- /dev/null +++ b/tests/autosar/deps/cli/run.wit @@ -0,0 +1,4 @@ +interface run { + /// Run the program. + run: func() -> result; +} diff --git a/tests/autosar/deps/cli/stdio.wit b/tests/autosar/deps/cli/stdio.wit new file mode 100644 index 000000000..1b653b6e2 --- /dev/null +++ b/tests/autosar/deps/cli/stdio.wit @@ -0,0 +1,17 @@ +interface stdin { + use wasi:io/streams@0.2.0-rc-2023-11-10.{input-stream}; + + get-stdin: func() -> input-stream; +} + +interface stdout { + use wasi:io/streams@0.2.0-rc-2023-11-10.{output-stream}; + + get-stdout: func() -> output-stream; +} + +interface stderr { + use wasi:io/streams@0.2.0-rc-2023-11-10.{output-stream}; + + get-stderr: func() -> output-stream; +} diff --git a/tests/autosar/deps/cli/terminal.wit b/tests/autosar/deps/cli/terminal.wit new file mode 100644 index 000000000..47495769b --- /dev/null +++ b/tests/autosar/deps/cli/terminal.wit @@ -0,0 +1,47 @@ +interface terminal-input { + /// The input side of a terminal. + resource terminal-input; + + // In the future, this may include functions for disabling echoing, + // disabling input buffering so that keyboard events are sent through + // immediately, querying supported features, and so on. +} + +interface terminal-output { + /// The output side of a terminal. + resource terminal-output; + + // In the future, this may include functions for querying the terminal + // size, being notified of terminal size changes, querying supported + // features, and so on. +} + +/// An interface providing an optional `terminal-input` for stdin as a +/// link-time authority. +interface terminal-stdin { + use terminal-input.{terminal-input}; + + /// If stdin is connected to a terminal, return a `terminal-input` handle + /// allowing further interaction with it. + get-terminal-stdin: func() -> option; +} + +/// An interface providing an optional `terminal-output` for stdout as a +/// link-time authority. +interface terminal-stdout { + use terminal-output.{terminal-output}; + + /// If stdout is connected to a terminal, return a `terminal-output` handle + /// allowing further interaction with it. + get-terminal-stdout: func() -> option; +} + +/// An interface providing an optional `terminal-output` for stderr as a +/// link-time authority. +interface terminal-stderr { + use terminal-output.{terminal-output}; + + /// If stderr is connected to a terminal, return a `terminal-output` handle + /// allowing further interaction with it. + get-terminal-stderr: func() -> option; +} diff --git a/tests/autosar/deps/clocks/monotonic-clock.wit b/tests/autosar/deps/clocks/monotonic-clock.wit new file mode 100644 index 000000000..09ef32c36 --- /dev/null +++ b/tests/autosar/deps/clocks/monotonic-clock.wit @@ -0,0 +1,45 @@ +package wasi:clocks@0.2.0-rc-2023-11-10; +/// WASI Monotonic Clock is a clock API intended to let users measure elapsed +/// time. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +/// +/// A monotonic clock is a clock which has an unspecified initial value, and +/// successive reads of the clock will produce non-decreasing values. +/// +/// It is intended for measuring elapsed time. +interface monotonic-clock { + use wasi:io/poll@0.2.0-rc-2023-11-10.{pollable}; + + /// An instant in time, in nanoseconds. An instant is relative to an + /// unspecified initial value, and can only be compared to instances from + /// the same monotonic-clock. + type instant = u64; + + /// A duration of time, in nanoseconds. + type duration = u64; + + /// Read the current value of the clock. + /// + /// The clock is monotonic, therefore calling this function repeatedly will + /// produce a sequence of non-decreasing values. + now: func() -> instant; + + /// Query the resolution of the clock. Returns the duration of time + /// corresponding to a clock tick. + resolution: func() -> duration; + + /// Create a `pollable` which will resolve once the specified instant + /// occured. + subscribe-instant: func( + when: instant, + ) -> pollable; + + /// Create a `pollable` which will resolve once the given duration has + /// elapsed, starting at the time at which this function was called. + /// occured. + subscribe-duration: func( + when: duration, + ) -> pollable; +} diff --git a/tests/autosar/deps/clocks/wall-clock.wit b/tests/autosar/deps/clocks/wall-clock.wit new file mode 100644 index 000000000..8abb9a0c0 --- /dev/null +++ b/tests/autosar/deps/clocks/wall-clock.wit @@ -0,0 +1,42 @@ +package wasi:clocks@0.2.0-rc-2023-11-10; +/// WASI Wall Clock is a clock API intended to let users query the current +/// time. The name "wall" makes an analogy to a "clock on the wall", which +/// is not necessarily monotonic as it may be reset. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +/// +/// A wall clock is a clock which measures the date and time according to +/// some external reference. +/// +/// External references may be reset, so this clock is not necessarily +/// monotonic, making it unsuitable for measuring elapsed time. +/// +/// It is intended for reporting the current date and time for humans. +interface wall-clock { + /// A time and date in seconds plus nanoseconds. + record datetime { + seconds: u64, + nanoseconds: u32, + } + + /// Read the current value of the clock. + /// + /// This clock is not monotonic, therefore calling this function repeatedly + /// will not necessarily produce a sequence of non-decreasing values. + /// + /// The returned timestamps represent the number of seconds since + /// 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch], + /// also known as [Unix Time]. + /// + /// The nanoseconds field of the output is always less than 1000000000. + /// + /// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 + /// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time + now: func() -> datetime; + + /// Query the resolution of the clock. + /// + /// The nanoseconds field of the output is always less than 1000000000. + resolution: func() -> datetime; +} diff --git a/tests/autosar/deps/clocks/world.wit b/tests/autosar/deps/clocks/world.wit new file mode 100644 index 000000000..8fa080f0e --- /dev/null +++ b/tests/autosar/deps/clocks/world.wit @@ -0,0 +1,6 @@ +package wasi:clocks@0.2.0-rc-2023-11-10; + +world imports { + import monotonic-clock; + import wall-clock; +} diff --git a/tests/autosar/deps/filesystem/preopens.wit b/tests/autosar/deps/filesystem/preopens.wit new file mode 100644 index 000000000..95ec67843 --- /dev/null +++ b/tests/autosar/deps/filesystem/preopens.wit @@ -0,0 +1,8 @@ +package wasi:filesystem@0.2.0-rc-2023-11-10; + +interface preopens { + use types.{descriptor}; + + /// Return the set of preopened directories, and their path. + get-directories: func() -> list>; +} diff --git a/tests/autosar/deps/filesystem/types.wit b/tests/autosar/deps/filesystem/types.wit new file mode 100644 index 000000000..059722ab8 --- /dev/null +++ b/tests/autosar/deps/filesystem/types.wit @@ -0,0 +1,634 @@ +package wasi:filesystem@0.2.0-rc-2023-11-10; +/// WASI filesystem is a filesystem API primarily intended to let users run WASI +/// programs that access their files on their existing filesystems, without +/// significant overhead. +/// +/// It is intended to be roughly portable between Unix-family platforms and +/// Windows, though it does not hide many of the major differences. +/// +/// Paths are passed as interface-type `string`s, meaning they must consist of +/// a sequence of Unicode Scalar Values (USVs). Some filesystems may contain +/// paths which are not accessible by this API. +/// +/// The directory separator in WASI is always the forward-slash (`/`). +/// +/// All paths in WASI are relative paths, and are interpreted relative to a +/// `descriptor` referring to a base directory. If a `path` argument to any WASI +/// function starts with `/`, or if any step of resolving a `path`, including +/// `..` and symbolic link steps, reaches a directory outside of the base +/// directory, or reaches a symlink to an absolute or rooted path in the +/// underlying filesystem, the function fails with `error-code::not-permitted`. +/// +/// For more information about WASI path resolution and sandboxing, see +/// [WASI filesystem path resolution]. +/// +/// [WASI filesystem path resolution]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md +interface types { + use wasi:io/streams@0.2.0-rc-2023-11-10.{input-stream, output-stream, error}; + use wasi:clocks/wall-clock@0.2.0-rc-2023-11-10.{datetime}; + + /// File size or length of a region within a file. + type filesize = u64; + + /// The type of a filesystem object referenced by a descriptor. + /// + /// Note: This was called `filetype` in earlier versions of WASI. + enum descriptor-type { + /// The type of the descriptor or file is unknown or is different from + /// any of the other types specified. + unknown, + /// The descriptor refers to a block device inode. + block-device, + /// The descriptor refers to a character device inode. + character-device, + /// The descriptor refers to a directory inode. + directory, + /// The descriptor refers to a named pipe. + fifo, + /// The file refers to a symbolic link inode. + symbolic-link, + /// The descriptor refers to a regular file inode. + regular-file, + /// The descriptor refers to a socket. + socket, + } + + /// Descriptor flags. + /// + /// Note: This was called `fdflags` in earlier versions of WASI. + flags descriptor-flags { + /// Read mode: Data can be read. + read, + /// Write mode: Data can be written to. + write, + /// Request that writes be performed according to synchronized I/O file + /// integrity completion. The data stored in the file and the file's + /// metadata are synchronized. This is similar to `O_SYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + file-integrity-sync, + /// Request that writes be performed according to synchronized I/O data + /// integrity completion. Only the data stored in the file is + /// synchronized. This is similar to `O_DSYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + data-integrity-sync, + /// Requests that reads be performed at the same level of integrety + /// requested for writes. This is similar to `O_RSYNC` in POSIX. + /// + /// The precise semantics of this operation have not yet been defined for + /// WASI. At this time, it should be interpreted as a request, and not a + /// requirement. + requested-write-sync, + /// Mutating directories mode: Directory contents may be mutated. + /// + /// When this flag is unset on a descriptor, operations using the + /// descriptor which would create, rename, delete, modify the data or + /// metadata of filesystem objects, or obtain another handle which + /// would permit any of those, shall fail with `error-code::read-only` if + /// they would otherwise succeed. + /// + /// This may only be set on directories. + mutate-directory, + } + + /// File attributes. + /// + /// Note: This was called `filestat` in earlier versions of WASI. + record descriptor-stat { + /// File type. + %type: descriptor-type, + /// Number of hard links to the file. + link-count: link-count, + /// For regular files, the file size in bytes. For symbolic links, the + /// length in bytes of the pathname contained in the symbolic link. + size: filesize, + /// Last data access timestamp. + /// + /// If the `option` is none, the platform doesn't maintain an access + /// timestamp for this file. + data-access-timestamp: option, + /// Last data modification timestamp. + /// + /// If the `option` is none, the platform doesn't maintain a + /// modification timestamp for this file. + data-modification-timestamp: option, + /// Last file status-change timestamp. + /// + /// If the `option` is none, the platform doesn't maintain a + /// status-change timestamp for this file. + status-change-timestamp: option, + } + + /// Flags determining the method of how paths are resolved. + flags path-flags { + /// As long as the resolved path corresponds to a symbolic link, it is + /// expanded. + symlink-follow, + } + + /// Open flags used by `open-at`. + flags open-flags { + /// Create file if it does not exist, similar to `O_CREAT` in POSIX. + create, + /// Fail if not a directory, similar to `O_DIRECTORY` in POSIX. + directory, + /// Fail if file already exists, similar to `O_EXCL` in POSIX. + exclusive, + /// Truncate file to size 0, similar to `O_TRUNC` in POSIX. + truncate, + } + + /// Number of hard links to an inode. + type link-count = u64; + + /// When setting a timestamp, this gives the value to set it to. + variant new-timestamp { + /// Leave the timestamp set to its previous value. + no-change, + /// Set the timestamp to the current time of the system clock associated + /// with the filesystem. + now, + /// Set the timestamp to the given value. + timestamp(datetime), + } + + /// A directory entry. + record directory-entry { + /// The type of the file referred to by this directory entry. + %type: descriptor-type, + + /// The name of the object. + name: string, + } + + /// Error codes returned by functions, similar to `errno` in POSIX. + /// Not all of these error codes are returned by the functions provided by this + /// API; some are used in higher-level library layers, and others are provided + /// merely for alignment with POSIX. + enum error-code { + /// Permission denied, similar to `EACCES` in POSIX. + access, + /// Resource unavailable, or operation would block, similar to `EAGAIN` and `EWOULDBLOCK` in POSIX. + would-block, + /// Connection already in progress, similar to `EALREADY` in POSIX. + already, + /// Bad descriptor, similar to `EBADF` in POSIX. + bad-descriptor, + /// Device or resource busy, similar to `EBUSY` in POSIX. + busy, + /// Resource deadlock would occur, similar to `EDEADLK` in POSIX. + deadlock, + /// Storage quota exceeded, similar to `EDQUOT` in POSIX. + quota, + /// File exists, similar to `EEXIST` in POSIX. + exist, + /// File too large, similar to `EFBIG` in POSIX. + file-too-large, + /// Illegal byte sequence, similar to `EILSEQ` in POSIX. + illegal-byte-sequence, + /// Operation in progress, similar to `EINPROGRESS` in POSIX. + in-progress, + /// Interrupted function, similar to `EINTR` in POSIX. + interrupted, + /// Invalid argument, similar to `EINVAL` in POSIX. + invalid, + /// I/O error, similar to `EIO` in POSIX. + io, + /// Is a directory, similar to `EISDIR` in POSIX. + is-directory, + /// Too many levels of symbolic links, similar to `ELOOP` in POSIX. + loop, + /// Too many links, similar to `EMLINK` in POSIX. + too-many-links, + /// Message too large, similar to `EMSGSIZE` in POSIX. + message-size, + /// Filename too long, similar to `ENAMETOOLONG` in POSIX. + name-too-long, + /// No such device, similar to `ENODEV` in POSIX. + no-device, + /// No such file or directory, similar to `ENOENT` in POSIX. + no-entry, + /// No locks available, similar to `ENOLCK` in POSIX. + no-lock, + /// Not enough space, similar to `ENOMEM` in POSIX. + insufficient-memory, + /// No space left on device, similar to `ENOSPC` in POSIX. + insufficient-space, + /// Not a directory or a symbolic link to a directory, similar to `ENOTDIR` in POSIX. + not-directory, + /// Directory not empty, similar to `ENOTEMPTY` in POSIX. + not-empty, + /// State not recoverable, similar to `ENOTRECOVERABLE` in POSIX. + not-recoverable, + /// Not supported, similar to `ENOTSUP` and `ENOSYS` in POSIX. + unsupported, + /// Inappropriate I/O control operation, similar to `ENOTTY` in POSIX. + no-tty, + /// No such device or address, similar to `ENXIO` in POSIX. + no-such-device, + /// Value too large to be stored in data type, similar to `EOVERFLOW` in POSIX. + overflow, + /// Operation not permitted, similar to `EPERM` in POSIX. + not-permitted, + /// Broken pipe, similar to `EPIPE` in POSIX. + pipe, + /// Read-only file system, similar to `EROFS` in POSIX. + read-only, + /// Invalid seek, similar to `ESPIPE` in POSIX. + invalid-seek, + /// Text file busy, similar to `ETXTBSY` in POSIX. + text-file-busy, + /// Cross-device link, similar to `EXDEV` in POSIX. + cross-device, + } + + /// File or memory access pattern advisory information. + enum advice { + /// The application has no advice to give on its behavior with respect + /// to the specified data. + normal, + /// The application expects to access the specified data sequentially + /// from lower offsets to higher offsets. + sequential, + /// The application expects to access the specified data in a random + /// order. + random, + /// The application expects to access the specified data in the near + /// future. + will-need, + /// The application expects that it will not access the specified data + /// in the near future. + dont-need, + /// The application expects to access the specified data once and then + /// not reuse it thereafter. + no-reuse, + } + + /// A 128-bit hash value, split into parts because wasm doesn't have a + /// 128-bit integer type. + record metadata-hash-value { + /// 64 bits of a 128-bit hash value. + lower: u64, + /// Another 64 bits of a 128-bit hash value. + upper: u64, + } + + /// A descriptor is a reference to a filesystem object, which may be a file, + /// directory, named pipe, special file, or other object on which filesystem + /// calls may be made. + resource descriptor { + /// Return a stream for reading from a file, if available. + /// + /// May fail with an error-code describing why the file cannot be read. + /// + /// Multiple read, write, and append streams may be active on the same open + /// file and they do not interfere with each other. + /// + /// Note: This allows using `read-stream`, which is similar to `read` in POSIX. + read-via-stream: func( + /// The offset within the file at which to start reading. + offset: filesize, + ) -> result; + + /// Return a stream for writing to a file, if available. + /// + /// May fail with an error-code describing why the file cannot be written. + /// + /// Note: This allows using `write-stream`, which is similar to `write` in + /// POSIX. + write-via-stream: func( + /// The offset within the file at which to start writing. + offset: filesize, + ) -> result; + + /// Return a stream for appending to a file, if available. + /// + /// May fail with an error-code describing why the file cannot be appended. + /// + /// Note: This allows using `write-stream`, which is similar to `write` with + /// `O_APPEND` in in POSIX. + append-via-stream: func() -> result; + + /// Provide file advisory information on a descriptor. + /// + /// This is similar to `posix_fadvise` in POSIX. + advise: func( + /// The offset within the file to which the advisory applies. + offset: filesize, + /// The length of the region to which the advisory applies. + length: filesize, + /// The advice. + advice: advice + ) -> result<_, error-code>; + + /// Synchronize the data of a file to disk. + /// + /// This function succeeds with no effect if the file descriptor is not + /// opened for writing. + /// + /// Note: This is similar to `fdatasync` in POSIX. + sync-data: func() -> result<_, error-code>; + + /// Get flags associated with a descriptor. + /// + /// Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX. + /// + /// Note: This returns the value that was the `fs_flags` value returned + /// from `fdstat_get` in earlier versions of WASI. + get-flags: func() -> result; + + /// Get the dynamic type of a descriptor. + /// + /// Note: This returns the same value as the `type` field of the `fd-stat` + /// returned by `stat`, `stat-at` and similar. + /// + /// Note: This returns similar flags to the `st_mode & S_IFMT` value provided + /// by `fstat` in POSIX. + /// + /// Note: This returns the value that was the `fs_filetype` value returned + /// from `fdstat_get` in earlier versions of WASI. + get-type: func() -> result; + + /// Adjust the size of an open file. If this increases the file's size, the + /// extra bytes are filled with zeros. + /// + /// Note: This was called `fd_filestat_set_size` in earlier versions of WASI. + set-size: func(size: filesize) -> result<_, error-code>; + + /// Adjust the timestamps of an open file or directory. + /// + /// Note: This is similar to `futimens` in POSIX. + /// + /// Note: This was called `fd_filestat_set_times` in earlier versions of WASI. + set-times: func( + /// The desired values of the data access timestamp. + data-access-timestamp: new-timestamp, + /// The desired values of the data modification timestamp. + data-modification-timestamp: new-timestamp, + ) -> result<_, error-code>; + + /// Read from a descriptor, without using and updating the descriptor's offset. + /// + /// This function returns a list of bytes containing the data that was + /// read, along with a bool which, when true, indicates that the end of the + /// file was reached. The returned list will contain up to `length` bytes; it + /// may return fewer than requested, if the end of the file is reached or + /// if the I/O operation is interrupted. + /// + /// In the future, this may change to return a `stream`. + /// + /// Note: This is similar to `pread` in POSIX. + read: func( + /// The maximum number of bytes to read. + length: filesize, + /// The offset within the file at which to read. + offset: filesize, + ) -> result, bool>, error-code>; + + /// Write to a descriptor, without using and updating the descriptor's offset. + /// + /// It is valid to write past the end of a file; the file is extended to the + /// extent of the write, with bytes between the previous end and the start of + /// the write set to zero. + /// + /// In the future, this may change to take a `stream`. + /// + /// Note: This is similar to `pwrite` in POSIX. + write: func( + /// Data to write + buffer: list, + /// The offset within the file at which to write. + offset: filesize, + ) -> result; + + /// Read directory entries from a directory. + /// + /// On filesystems where directories contain entries referring to themselves + /// and their parents, often named `.` and `..` respectively, these entries + /// are omitted. + /// + /// This always returns a new stream which starts at the beginning of the + /// directory. Multiple streams may be active on the same directory, and they + /// do not interfere with each other. + read-directory: func() -> result; + + /// Synchronize the data and metadata of a file to disk. + /// + /// This function succeeds with no effect if the file descriptor is not + /// opened for writing. + /// + /// Note: This is similar to `fsync` in POSIX. + sync: func() -> result<_, error-code>; + + /// Create a directory. + /// + /// Note: This is similar to `mkdirat` in POSIX. + create-directory-at: func( + /// The relative path at which to create the directory. + path: string, + ) -> result<_, error-code>; + + /// Return the attributes of an open file or directory. + /// + /// Note: This is similar to `fstat` in POSIX, except that it does not return + /// device and inode information. For testing whether two descriptors refer to + /// the same underlying filesystem object, use `is-same-object`. To obtain + /// additional data that can be used do determine whether a file has been + /// modified, use `metadata-hash`. + /// + /// Note: This was called `fd_filestat_get` in earlier versions of WASI. + stat: func() -> result; + + /// Return the attributes of a file or directory. + /// + /// Note: This is similar to `fstatat` in POSIX, except that it does not + /// return device and inode information. See the `stat` description for a + /// discussion of alternatives. + /// + /// Note: This was called `path_filestat_get` in earlier versions of WASI. + stat-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the file or directory to inspect. + path: string, + ) -> result; + + /// Adjust the timestamps of a file or directory. + /// + /// Note: This is similar to `utimensat` in POSIX. + /// + /// Note: This was called `path_filestat_set_times` in earlier versions of + /// WASI. + set-times-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the file or directory to operate on. + path: string, + /// The desired values of the data access timestamp. + data-access-timestamp: new-timestamp, + /// The desired values of the data modification timestamp. + data-modification-timestamp: new-timestamp, + ) -> result<_, error-code>; + + /// Create a hard link. + /// + /// Note: This is similar to `linkat` in POSIX. + link-at: func( + /// Flags determining the method of how the path is resolved. + old-path-flags: path-flags, + /// The relative source path from which to link. + old-path: string, + /// The base directory for `new-path`. + new-descriptor: borrow, + /// The relative destination path at which to create the hard link. + new-path: string, + ) -> result<_, error-code>; + + /// Open a file or directory. + /// + /// The returned descriptor is not guaranteed to be the lowest-numbered + /// descriptor not currently open/ it is randomized to prevent applications + /// from depending on making assumptions about indexes, since this is + /// error-prone in multi-threaded contexts. The returned descriptor is + /// guaranteed to be less than 2**31. + /// + /// If `flags` contains `descriptor-flags::mutate-directory`, and the base + /// descriptor doesn't have `descriptor-flags::mutate-directory` set, + /// `open-at` fails with `error-code::read-only`. + /// + /// If `flags` contains `write` or `mutate-directory`, or `open-flags` + /// contains `truncate` or `create`, and the base descriptor doesn't have + /// `descriptor-flags::mutate-directory` set, `open-at` fails with + /// `error-code::read-only`. + /// + /// Note: This is similar to `openat` in POSIX. + open-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the object to open. + path: string, + /// The method by which to open the file. + open-flags: open-flags, + /// Flags to use for the resulting descriptor. + %flags: descriptor-flags, + ) -> result; + + /// Read the contents of a symbolic link. + /// + /// If the contents contain an absolute or rooted path in the underlying + /// filesystem, this function fails with `error-code::not-permitted`. + /// + /// Note: This is similar to `readlinkat` in POSIX. + readlink-at: func( + /// The relative path of the symbolic link from which to read. + path: string, + ) -> result; + + /// Remove a directory. + /// + /// Return `error-code::not-empty` if the directory is not empty. + /// + /// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. + remove-directory-at: func( + /// The relative path to a directory to remove. + path: string, + ) -> result<_, error-code>; + + /// Rename a filesystem object. + /// + /// Note: This is similar to `renameat` in POSIX. + rename-at: func( + /// The relative source path of the file or directory to rename. + old-path: string, + /// The base directory for `new-path`. + new-descriptor: borrow, + /// The relative destination path to which to rename the file or directory. + new-path: string, + ) -> result<_, error-code>; + + /// Create a symbolic link (also known as a "symlink"). + /// + /// If `old-path` starts with `/`, the function fails with + /// `error-code::not-permitted`. + /// + /// Note: This is similar to `symlinkat` in POSIX. + symlink-at: func( + /// The contents of the symbolic link. + old-path: string, + /// The relative destination path at which to create the symbolic link. + new-path: string, + ) -> result<_, error-code>; + + /// Unlink a filesystem object that is not a directory. + /// + /// Return `error-code::is-directory` if the path refers to a directory. + /// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. + unlink-file-at: func( + /// The relative path to a file to unlink. + path: string, + ) -> result<_, error-code>; + + /// Test whether two descriptors refer to the same filesystem object. + /// + /// In POSIX, this corresponds to testing whether the two descriptors have the + /// same device (`st_dev`) and inode (`st_ino` or `d_ino`) numbers. + /// wasi-filesystem does not expose device and inode numbers, so this function + /// may be used instead. + is-same-object: func(other: borrow) -> bool; + + /// Return a hash of the metadata associated with a filesystem object referred + /// to by a descriptor. + /// + /// This returns a hash of the last-modification timestamp and file size, and + /// may also include the inode number, device number, birth timestamp, and + /// other metadata fields that may change when the file is modified or + /// replaced. It may also include a secret value chosen by the + /// implementation and not otherwise exposed. + /// + /// Implementations are encourated to provide the following properties: + /// + /// - If the file is not modified or replaced, the computed hash value should + /// usually not change. + /// - If the object is modified or replaced, the computed hash value should + /// usually change. + /// - The inputs to the hash should not be easily computable from the + /// computed hash. + /// + /// However, none of these is required. + metadata-hash: func() -> result; + + /// Return a hash of the metadata associated with a filesystem object referred + /// to by a directory descriptor and a relative path. + /// + /// This performs the same hash computation as `metadata-hash`. + metadata-hash-at: func( + /// Flags determining the method of how the path is resolved. + path-flags: path-flags, + /// The relative path of the file or directory to inspect. + path: string, + ) -> result; + } + + /// A stream of directory entries. + resource directory-entry-stream { + /// Read a single directory entry from a `directory-entry-stream`. + read-directory-entry: func() -> result, error-code>; + } + + /// Attempts to extract a filesystem-related `error-code` from the stream + /// `error` provided. + /// + /// Stream operations which return `stream-error::last-operation-failed` + /// have a payload with more information about the operation that failed. + /// This payload can be passed through to this function to see if there's + /// filesystem-related information about the error to return. + /// + /// Note that this function is fallible because not all stream-related + /// errors are filesystem-related errors. + filesystem-error-code: func(err: borrow) -> option; +} diff --git a/tests/autosar/deps/filesystem/world.wit b/tests/autosar/deps/filesystem/world.wit new file mode 100644 index 000000000..285e0bae9 --- /dev/null +++ b/tests/autosar/deps/filesystem/world.wit @@ -0,0 +1,6 @@ +package wasi:filesystem@0.2.0-rc-2023-11-10; + +world imports { + import types; + import preopens; +} diff --git a/tests/autosar/deps/io/error.wit b/tests/autosar/deps/io/error.wit new file mode 100644 index 000000000..31918acbb --- /dev/null +++ b/tests/autosar/deps/io/error.wit @@ -0,0 +1,34 @@ +package wasi:io@0.2.0-rc-2023-11-10; + + +interface error { + /// A resource which represents some error information. + /// + /// The only method provided by this resource is `to-debug-string`, + /// which provides some human-readable information about the error. + /// + /// In the `wasi:io` package, this resource is returned through the + /// `wasi:io/streams/stream-error` type. + /// + /// To provide more specific error information, other interfaces may + /// provide functions to further "downcast" this error into more specific + /// error information. For example, `error`s returned in streams derived + /// from filesystem types to be described using the filesystem's own + /// error-code type, using the function + /// `wasi:filesystem/types/filesystem-error-code`, which takes a parameter + /// `borrow` and returns + /// `option`. + /// + /// The set of functions which can "downcast" an `error` into a more + /// concrete type is open. + resource error { + /// Returns a string that is suitable to assist humans in debugging + /// this error. + /// + /// WARNING: The returned string should not be consumed mechanically! + /// It may change across platforms, hosts, or other implementation + /// details. Parsing this string is a major platform-compatibility + /// hazard. + to-debug-string: func() -> string; + } +} diff --git a/tests/autosar/deps/io/poll.wit b/tests/autosar/deps/io/poll.wit new file mode 100644 index 000000000..81b1cab99 --- /dev/null +++ b/tests/autosar/deps/io/poll.wit @@ -0,0 +1,41 @@ +package wasi:io@0.2.0-rc-2023-11-10; + +/// A poll API intended to let users wait for I/O events on multiple handles +/// at once. +interface poll { + /// `pollable` represents a single I/O event which may be ready, or not. + resource pollable { + + /// Return the readiness of a pollable. This function never blocks. + /// + /// Returns `true` when the pollable is ready, and `false` otherwise. + ready: func() -> bool; + + /// `block` returns immediately if the pollable is ready, and otherwise + /// blocks until ready. + /// + /// This function is equivalent to calling `poll.poll` on a list + /// containing only this pollable. + block: func(); + } + + /// Poll for completion on a set of pollables. + /// + /// This function takes a list of pollables, which identify I/O sources of + /// interest, and waits until one or more of the events is ready for I/O. + /// + /// The result `list` contains one or more indices of handles in the + /// argument list that is ready for I/O. + /// + /// If the list contains more elements than can be indexed with a `u32` + /// value, this function traps. + /// + /// A timeout can be implemented by adding a pollable from the + /// wasi-clocks API to the list. + /// + /// This function does not return a `result`; polling in itself does not + /// do any I/O so it doesn't fail. If any of the I/O sources identified by + /// the pollables has an error, it is indicated by marking the source as + /// being reaedy for I/O. + poll: func(in: list>) -> list; +} diff --git a/tests/autosar/deps/io/streams.wit b/tests/autosar/deps/io/streams.wit new file mode 100644 index 000000000..f6f7fe0e8 --- /dev/null +++ b/tests/autosar/deps/io/streams.wit @@ -0,0 +1,251 @@ +package wasi:io@0.2.0-rc-2023-11-10; + +/// WASI I/O is an I/O abstraction API which is currently focused on providing +/// stream types. +/// +/// In the future, the component model is expected to add built-in stream types; +/// when it does, they are expected to subsume this API. +interface streams { + use error.{error}; + use poll.{pollable}; + + /// An error for input-stream and output-stream operations. + variant stream-error { + /// The last operation (a write or flush) failed before completion. + /// + /// More information is available in the `error` payload. + last-operation-failed(error), + /// The stream is closed: no more input will be accepted by the + /// stream. A closed output-stream will return this error on all + /// future operations. + closed + } + + /// An input bytestream. + /// + /// `input-stream`s are *non-blocking* to the extent practical on underlying + /// platforms. I/O operations always return promptly; if fewer bytes are + /// promptly available than requested, they return the number of bytes promptly + /// available, which could even be zero. To wait for data to be available, + /// use the `subscribe` function to obtain a `pollable` which can be polled + /// for using `wasi:io/poll`. + resource input-stream { + /// Perform a non-blocking read from the stream. + /// + /// This function returns a list of bytes containing the read data, + /// when successful. The returned list will contain up to `len` bytes; + /// it may return fewer than requested, but not more. The list is + /// empty when no bytes are available for reading at this time. The + /// pollable given by `subscribe` will be ready when more bytes are + /// available. + /// + /// This function fails with a `stream-error` when the operation + /// encounters an error, giving `last-operation-failed`, or when the + /// stream is closed, giving `closed`. + /// + /// When the caller gives a `len` of 0, it represents a request to + /// read 0 bytes. If the stream is still open, this call should + /// succeed and return an empty list, or otherwise fail with `closed`. + /// + /// The `len` parameter is a `u64`, which could represent a list of u8 which + /// is not possible to allocate in wasm32, or not desirable to allocate as + /// as a return value by the callee. The callee may return a list of bytes + /// less than `len` in size while more bytes are available for reading. + read: func( + /// The maximum number of bytes to read + len: u64 + ) -> result, stream-error>; + + /// Read bytes from a stream, after blocking until at least one byte can + /// be read. Except for blocking, behavior is identical to `read`. + blocking-read: func( + /// The maximum number of bytes to read + len: u64 + ) -> result, stream-error>; + + /// Skip bytes from a stream. Returns number of bytes skipped. + /// + /// Behaves identical to `read`, except instead of returning a list + /// of bytes, returns the number of bytes consumed from the stream. + skip: func( + /// The maximum number of bytes to skip. + len: u64, + ) -> result; + + /// Skip bytes from a stream, after blocking until at least one byte + /// can be skipped. Except for blocking behavior, identical to `skip`. + blocking-skip: func( + /// The maximum number of bytes to skip. + len: u64, + ) -> result; + + /// Create a `pollable` which will resolve once either the specified stream + /// has bytes available to read or the other end of the stream has been + /// closed. + /// The created `pollable` is a child resource of the `input-stream`. + /// Implementations may trap if the `input-stream` is dropped before + /// all derived `pollable`s created with this function are dropped. + subscribe: func() -> pollable; + } + + + /// An output bytestream. + /// + /// `output-stream`s are *non-blocking* to the extent practical on + /// underlying platforms. Except where specified otherwise, I/O operations also + /// always return promptly, after the number of bytes that can be written + /// promptly, which could even be zero. To wait for the stream to be ready to + /// accept data, the `subscribe` function to obtain a `pollable` which can be + /// polled for using `wasi:io/poll`. + resource output-stream { + /// Check readiness for writing. This function never blocks. + /// + /// Returns the number of bytes permitted for the next call to `write`, + /// or an error. Calling `write` with more bytes than this function has + /// permitted will trap. + /// + /// When this function returns 0 bytes, the `subscribe` pollable will + /// become ready when this function will report at least 1 byte, or an + /// error. + check-write: func() -> result; + + /// Perform a write. This function never blocks. + /// + /// Precondition: check-write gave permit of Ok(n) and contents has a + /// length of less than or equal to n. Otherwise, this function will trap. + /// + /// returns Err(closed) without writing if the stream has closed since + /// the last call to check-write provided a permit. + write: func( + contents: list + ) -> result<_, stream-error>; + + /// Perform a write of up to 4096 bytes, and then flush the stream. Block + /// until all of these operations are complete, or an error occurs. + /// + /// This is a convenience wrapper around the use of `check-write`, + /// `subscribe`, `write`, and `flush`, and is implemented with the + /// following pseudo-code: + /// + /// ```text + /// let pollable = this.subscribe(); + /// while !contents.is_empty() { + /// // Wait for the stream to become writable + /// pollable.block(); + /// let Ok(n) = this.check-write(); // eliding error handling + /// let len = min(n, contents.len()); + /// let (chunk, rest) = contents.split_at(len); + /// this.write(chunk ); // eliding error handling + /// contents = rest; + /// } + /// this.flush(); + /// // Wait for completion of `flush` + /// pollable.block(); + /// // Check for any errors that arose during `flush` + /// let _ = this.check-write(); // eliding error handling + /// ``` + blocking-write-and-flush: func( + contents: list + ) -> result<_, stream-error>; + + /// Request to flush buffered output. This function never blocks. + /// + /// This tells the output-stream that the caller intends any buffered + /// output to be flushed. the output which is expected to be flushed + /// is all that has been passed to `write` prior to this call. + /// + /// Upon calling this function, the `output-stream` will not accept any + /// writes (`check-write` will return `ok(0)`) until the flush has + /// completed. The `subscribe` pollable will become ready when the + /// flush has completed and the stream can accept more writes. + flush: func() -> result<_, stream-error>; + + /// Request to flush buffered output, and block until flush completes + /// and stream is ready for writing again. + blocking-flush: func() -> result<_, stream-error>; + + /// Create a `pollable` which will resolve once the output-stream + /// is ready for more writing, or an error has occured. When this + /// pollable is ready, `check-write` will return `ok(n)` with n>0, or an + /// error. + /// + /// If the stream is closed, this pollable is always ready immediately. + /// + /// The created `pollable` is a child resource of the `output-stream`. + /// Implementations may trap if the `output-stream` is dropped before + /// all derived `pollable`s created with this function are dropped. + subscribe: func() -> pollable; + + /// Write zeroes to a stream. + /// + /// This should be used precisely like `write` with the exact same + /// preconditions (must use check-write first), but instead of + /// passing a list of bytes, you simply pass the number of zero-bytes + /// that should be written. + write-zeroes: func( + /// The number of zero-bytes to write + len: u64 + ) -> result<_, stream-error>; + + /// Perform a write of up to 4096 zeroes, and then flush the stream. + /// Block until all of these operations are complete, or an error + /// occurs. + /// + /// This is a convenience wrapper around the use of `check-write`, + /// `subscribe`, `write-zeroes`, and `flush`, and is implemented with + /// the following pseudo-code: + /// + /// ```text + /// let pollable = this.subscribe(); + /// while num_zeroes != 0 { + /// // Wait for the stream to become writable + /// pollable.block(); + /// let Ok(n) = this.check-write(); // eliding error handling + /// let len = min(n, num_zeroes); + /// this.write-zeroes(len); // eliding error handling + /// num_zeroes -= len; + /// } + /// this.flush(); + /// // Wait for completion of `flush` + /// pollable.block(); + /// // Check for any errors that arose during `flush` + /// let _ = this.check-write(); // eliding error handling + /// ``` + blocking-write-zeroes-and-flush: func( + /// The number of zero-bytes to write + len: u64 + ) -> result<_, stream-error>; + + /// Read from one stream and write to another. + /// + /// The behavior of splice is equivelant to: + /// 1. calling `check-write` on the `output-stream` + /// 2. calling `read` on the `input-stream` with the smaller of the + /// `check-write` permitted length and the `len` provided to `splice` + /// 3. calling `write` on the `output-stream` with that read data. + /// + /// Any error reported by the call to `check-write`, `read`, or + /// `write` ends the splice and reports that error. + /// + /// This function returns the number of bytes transferred; it may be less + /// than `len`. + splice: func( + /// The stream to read from + src: borrow, + /// The number of bytes to splice + len: u64, + ) -> result; + + /// Read from one stream and write to another, with blocking. + /// + /// This is similar to `splice`, except that it blocks until the + /// `output-stream` is ready for writing, and the `input-stream` + /// is ready for reading, before performing the `splice`. + blocking-splice: func( + /// The stream to read from + src: borrow, + /// The number of bytes to splice + len: u64, + ) -> result; + } +} diff --git a/tests/autosar/deps/io/world.wit b/tests/autosar/deps/io/world.wit new file mode 100644 index 000000000..8243da2ee --- /dev/null +++ b/tests/autosar/deps/io/world.wit @@ -0,0 +1,6 @@ +package wasi:io@0.2.0-rc-2023-11-10; + +world imports { + import streams; + import poll; +} diff --git a/tests/autosar/deps/random/insecure-seed.wit b/tests/autosar/deps/random/insecure-seed.wit new file mode 100644 index 000000000..f76e87dad --- /dev/null +++ b/tests/autosar/deps/random/insecure-seed.wit @@ -0,0 +1,25 @@ +package wasi:random@0.2.0-rc-2023-11-10; +/// The insecure-seed interface for seeding hash-map DoS resistance. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +interface insecure-seed { + /// Return a 128-bit value that may contain a pseudo-random value. + /// + /// The returned value is not required to be computed from a CSPRNG, and may + /// even be entirely deterministic. Host implementations are encouraged to + /// provide pseudo-random values to any program exposed to + /// attacker-controlled content, to enable DoS protection built into many + /// languages' hash-map implementations. + /// + /// This function is intended to only be called once, by a source language + /// to initialize Denial Of Service (DoS) protection in its hash-map + /// implementation. + /// + /// # Expected future evolution + /// + /// This will likely be changed to a value import, to prevent it from being + /// called multiple times and potentially used for purposes other than DoS + /// protection. + insecure-seed: func() -> tuple; +} diff --git a/tests/autosar/deps/random/insecure.wit b/tests/autosar/deps/random/insecure.wit new file mode 100644 index 000000000..ec7b99737 --- /dev/null +++ b/tests/autosar/deps/random/insecure.wit @@ -0,0 +1,22 @@ +package wasi:random@0.2.0-rc-2023-11-10; +/// The insecure interface for insecure pseudo-random numbers. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +interface insecure { + /// Return `len` insecure pseudo-random bytes. + /// + /// This function is not cryptographically secure. Do not use it for + /// anything related to security. + /// + /// There are no requirements on the values of the returned bytes, however + /// implementations are encouraged to return evenly distributed values with + /// a long period. + get-insecure-random-bytes: func(len: u64) -> list; + + /// Return an insecure pseudo-random `u64` value. + /// + /// This function returns the same type of pseudo-random data as + /// `get-insecure-random-bytes`, represented as a `u64`. + get-insecure-random-u64: func() -> u64; +} diff --git a/tests/autosar/deps/random/random.wit b/tests/autosar/deps/random/random.wit new file mode 100644 index 000000000..7a7dfa27a --- /dev/null +++ b/tests/autosar/deps/random/random.wit @@ -0,0 +1,26 @@ +package wasi:random@0.2.0-rc-2023-11-10; +/// WASI Random is a random data API. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +interface random { + /// Return `len` cryptographically-secure random or pseudo-random bytes. + /// + /// This function must produce data at least as cryptographically secure and + /// fast as an adequately seeded cryptographically-secure pseudo-random + /// number generator (CSPRNG). It must not block, from the perspective of + /// the calling program, under any circumstances, including on the first + /// request and on requests for numbers of bytes. The returned data must + /// always be unpredictable. + /// + /// This function must always return fresh data. Deterministic environments + /// must omit this function, rather than implementing it with deterministic + /// data. + get-random-bytes: func(len: u64) -> list; + + /// Return a cryptographically-secure random or pseudo-random `u64` value. + /// + /// This function returns the same type of data as `get-random-bytes`, + /// represented as a `u64`. + get-random-u64: func() -> u64; +} diff --git a/tests/autosar/deps/random/world.wit b/tests/autosar/deps/random/world.wit new file mode 100644 index 000000000..49e5743b4 --- /dev/null +++ b/tests/autosar/deps/random/world.wit @@ -0,0 +1,7 @@ +package wasi:random@0.2.0-rc-2023-11-10; + +world imports { + import random; + import insecure; + import insecure-seed; +} diff --git a/tests/autosar/deps/sockets/instance-network.wit b/tests/autosar/deps/sockets/instance-network.wit new file mode 100644 index 000000000..e455d0ff7 --- /dev/null +++ b/tests/autosar/deps/sockets/instance-network.wit @@ -0,0 +1,9 @@ + +/// This interface provides a value-export of the default network handle.. +interface instance-network { + use network.{network}; + + /// Get a handle to the default network. + instance-network: func() -> network; + +} diff --git a/tests/autosar/deps/sockets/ip-name-lookup.wit b/tests/autosar/deps/sockets/ip-name-lookup.wit new file mode 100644 index 000000000..931ccf7e0 --- /dev/null +++ b/tests/autosar/deps/sockets/ip-name-lookup.wit @@ -0,0 +1,51 @@ + +interface ip-name-lookup { + use wasi:io/poll@0.2.0-rc-2023-11-10.{pollable}; + use network.{network, error-code, ip-address}; + + + /// Resolve an internet host name to a list of IP addresses. + /// + /// Unicode domain names are automatically converted to ASCII using IDNA encoding. + /// If the input is an IP address string, the address is parsed and returned + /// as-is without making any external requests. + /// + /// See the wasi-socket proposal README.md for a comparison with getaddrinfo. + /// + /// This function never blocks. It either immediately fails or immediately + /// returns successfully with a `resolve-address-stream` that can be used + /// to (asynchronously) fetch the results. + /// + /// # Typical errors + /// - `invalid-argument`: `name` is a syntactically invalid domain name or IP address. + /// + /// # References: + /// - + /// - + /// - + /// - + resolve-addresses: func(network: borrow, name: string) -> result; + + resource resolve-address-stream { + /// Returns the next address from the resolver. + /// + /// This function should be called multiple times. On each call, it will + /// return the next address in connection order preference. If all + /// addresses have been exhausted, this function returns `none`. + /// + /// This function never returns IPv4-mapped IPv6 addresses. + /// + /// # Typical errors + /// - `name-unresolvable`: Name does not exist or has no suitable associated IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) + /// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. (EAI_AGAIN) + /// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. (EAI_FAIL) + /// - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) + resolve-next-address: func() -> result, error-code>; + + /// Create a `pollable` which will resolve once the stream is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; + } +} diff --git a/tests/autosar/deps/sockets/network.wit b/tests/autosar/deps/sockets/network.wit new file mode 100644 index 000000000..6bb07cd6f --- /dev/null +++ b/tests/autosar/deps/sockets/network.wit @@ -0,0 +1,147 @@ + +interface network { + /// An opaque resource that represents access to (a subset of) the network. + /// This enables context-based security for networking. + /// There is no need for this to map 1:1 to a physical network interface. + resource network; + + /// Error codes. + /// + /// In theory, every API can return any error code. + /// In practice, API's typically only return the errors documented per API + /// combined with a couple of errors that are always possible: + /// - `unknown` + /// - `access-denied` + /// - `not-supported` + /// - `out-of-memory` + /// - `concurrency-conflict` + /// + /// See each individual API for what the POSIX equivalents are. They sometimes differ per API. + enum error-code { + // ### GENERAL ERRORS ### + + /// Unknown error + unknown, + + /// Access denied. + /// + /// POSIX equivalent: EACCES, EPERM + access-denied, + + /// The operation is not supported. + /// + /// POSIX equivalent: EOPNOTSUPP + not-supported, + + /// One of the arguments is invalid. + /// + /// POSIX equivalent: EINVAL + invalid-argument, + + /// Not enough memory to complete the operation. + /// + /// POSIX equivalent: ENOMEM, ENOBUFS, EAI_MEMORY + out-of-memory, + + /// The operation timed out before it could finish completely. + timeout, + + /// This operation is incompatible with another asynchronous operation that is already in progress. + /// + /// POSIX equivalent: EALREADY + concurrency-conflict, + + /// Trying to finish an asynchronous operation that: + /// - has not been started yet, or: + /// - was already finished by a previous `finish-*` call. + /// + /// Note: this is scheduled to be removed when `future`s are natively supported. + not-in-progress, + + /// The operation has been aborted because it could not be completed immediately. + /// + /// Note: this is scheduled to be removed when `future`s are natively supported. + would-block, + + + + // ### TCP & UDP SOCKET ERRORS ### + + /// The operation is not valid in the socket's current state. + invalid-state, + + /// A new socket resource could not be created because of a system limit. + new-socket-limit, + + /// A bind operation failed because the provided address is not an address that the `network` can bind to. + address-not-bindable, + + /// A bind operation failed because the provided address is already in use or because there are no ephemeral ports available. + address-in-use, + + /// The remote address is not reachable + remote-unreachable, + + + // ### TCP SOCKET ERRORS ### + + /// The connection was forcefully rejected + connection-refused, + + /// The connection was reset. + connection-reset, + + /// A connection was aborted. + connection-aborted, + + + // ### UDP SOCKET ERRORS ### + datagram-too-large, + + + // ### NAME LOOKUP ERRORS ### + + /// Name does not exist or has no suitable associated IP addresses. + name-unresolvable, + + /// A temporary failure in name resolution occurred. + temporary-resolver-failure, + + /// A permanent failure in name resolution occurred. + permanent-resolver-failure, + } + + enum ip-address-family { + /// Similar to `AF_INET` in POSIX. + ipv4, + + /// Similar to `AF_INET6` in POSIX. + ipv6, + } + + type ipv4-address = tuple; + type ipv6-address = tuple; + + variant ip-address { + ipv4(ipv4-address), + ipv6(ipv6-address), + } + + record ipv4-socket-address { + port: u16, // sin_port + address: ipv4-address, // sin_addr + } + + record ipv6-socket-address { + port: u16, // sin6_port + flow-info: u32, // sin6_flowinfo + address: ipv6-address, // sin6_addr + scope-id: u32, // sin6_scope_id + } + + variant ip-socket-address { + ipv4(ipv4-socket-address), + ipv6(ipv6-socket-address), + } + +} diff --git a/tests/autosar/deps/sockets/tcp-create-socket.wit b/tests/autosar/deps/sockets/tcp-create-socket.wit new file mode 100644 index 000000000..768a07c85 --- /dev/null +++ b/tests/autosar/deps/sockets/tcp-create-socket.wit @@ -0,0 +1,26 @@ + +interface tcp-create-socket { + use network.{network, error-code, ip-address-family}; + use tcp.{tcp-socket}; + + /// Create a new TCP socket. + /// + /// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX. + /// + /// This function does not require a network capability handle. This is considered to be safe because + /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`listen`/`connect` + /// is called, the socket is effectively an in-memory configuration object, unable to communicate with the outside world. + /// + /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. + /// + /// # Typical errors + /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) + /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) + /// + /// # References + /// - + /// - + /// - + /// - + create-tcp-socket: func(address-family: ip-address-family) -> result; +} diff --git a/tests/autosar/deps/sockets/tcp.wit b/tests/autosar/deps/sockets/tcp.wit new file mode 100644 index 000000000..b01b65e6c --- /dev/null +++ b/tests/autosar/deps/sockets/tcp.wit @@ -0,0 +1,321 @@ + +interface tcp { + use wasi:io/streams@0.2.0-rc-2023-11-10.{input-stream, output-stream}; + use wasi:io/poll@0.2.0-rc-2023-11-10.{pollable}; + use wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-10.{duration}; + use network.{network, error-code, ip-socket-address, ip-address-family}; + + enum shutdown-type { + /// Similar to `SHUT_RD` in POSIX. + receive, + + /// Similar to `SHUT_WR` in POSIX. + send, + + /// Similar to `SHUT_RDWR` in POSIX. + both, + } + + + /// A TCP socket handle. + resource tcp-socket { + /// Bind the socket to a specific network on the provided IP address and port. + /// + /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which + /// network interface(s) to bind to. + /// If the TCP/UDP port is zero, the socket will be bound to a random free port. + /// + /// When a socket is not explicitly bound, the first invocation to a listen or connect operation will + /// implicitly bind the socket. + /// + /// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. + /// + /// # Typical `start` errors + /// - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) + /// - `invalid-argument`: `local-address` is not a unicast address. (EINVAL) + /// - `invalid-argument`: `local-address` is an IPv4-mapped IPv6 address, but the socket has `ipv6-only` enabled. (EINVAL) + /// - `invalid-state`: The socket is already bound. (EINVAL) + /// + /// # Typical `finish` errors + /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) + /// - `address-in-use`: Address is already in use. (EADDRINUSE) + /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) + /// - `not-in-progress`: A `bind` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; + finish-bind: func() -> result<_, error-code>; + + /// Connect to a remote endpoint. + /// + /// On success: + /// - the socket is transitioned into the Connection state + /// - a pair of streams is returned that can be used to read & write to the connection + /// + /// POSIX mentions: + /// > If connect() fails, the state of the socket is unspecified. Conforming applications should + /// > close the file descriptor and create a new socket before attempting to reconnect. + /// + /// WASI prescribes the following behavior: + /// - If `connect` fails because an input/state validation error, the socket should remain usable. + /// - If a connection was actually attempted but failed, the socket should become unusable for further network communication. + /// Besides `drop`, any method after such a failure may return an error. + /// + /// # Typical `start` errors + /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-argument`: `remote-address` is not a unicast address. (EINVAL, ENETUNREACH on Linux, EAFNOSUPPORT on MacOS) + /// - `invalid-argument`: `remote-address` is an IPv4-mapped IPv6 address, but the socket has `ipv6-only` enabled. (EINVAL, EADDRNOTAVAIL on Illumos) + /// - `invalid-argument`: `remote-address` is a non-IPv4-mapped IPv6 address, but the socket was bound to a specific IPv4-mapped IPv6 address. (or vice versa) + /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows) + /// - `invalid-argument`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL on Windows) + /// - `invalid-argument`: The socket is already attached to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`. + /// - `invalid-state`: The socket is already in the Connection state. (EISCONN) + /// - `invalid-state`: The socket is already in the Listener state. (EOPNOTSUPP, EINVAL on Windows) + /// + /// # Typical `finish` errors + /// - `timeout`: Connection timed out. (ETIMEDOUT) + /// - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED) + /// - `connection-reset`: The connection was reset. (ECONNRESET) + /// - `connection-aborted`: The connection was aborted. (ECONNABORTED) + /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) + /// - `not-in-progress`: A `connect` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + start-connect: func(network: borrow, remote-address: ip-socket-address) -> result<_, error-code>; + finish-connect: func() -> result, error-code>; + + /// Start listening for new connections. + /// + /// Transitions the socket into the Listener state. + /// + /// Unlike POSIX: + /// - this function is async. This enables interactive WASI hosts to inject permission prompts. + /// - the socket must already be explicitly bound. + /// + /// # Typical `start` errors + /// - `invalid-state`: The socket is not bound to any local address. (EDESTADDRREQ) + /// - `invalid-state`: The socket is already in the Connection state. (EISCONN, EINVAL on BSD) + /// - `invalid-state`: The socket is already in the Listener state. + /// + /// # Typical `finish` errors + /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE) + /// - `not-in-progress`: A `listen` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + start-listen: func() -> result<_, error-code>; + finish-listen: func() -> result<_, error-code>; + + /// Accept a new client socket. + /// + /// The returned socket is bound and in the Connection state. The following properties are inherited from the listener socket: + /// - `address-family` + /// - `ipv6-only` + /// - `keep-alive-enabled` + /// - `keep-alive-idle-time` + /// - `keep-alive-interval` + /// - `keep-alive-count` + /// - `hop-limit` + /// - `receive-buffer-size` + /// - `send-buffer-size` + /// + /// On success, this function returns the newly accepted client socket along with + /// a pair of streams that can be used to read & write to the connection. + /// + /// # Typical errors + /// - `invalid-state`: Socket is not in the Listener state. (EINVAL) + /// - `would-block`: No pending connections at the moment. (EWOULDBLOCK, EAGAIN) + /// - `connection-aborted`: An incoming connection was pending, but was terminated by the client before this listener could accept it. (ECONNABORTED) + /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) + /// + /// # References + /// - + /// - + /// - + /// - + accept: func() -> result, error-code>; + + /// Get the bound local address. + /// + /// POSIX mentions: + /// > If the socket has not been bound to a local name, the value + /// > stored in the object pointed to by `address` is unspecified. + /// + /// WASI is stricter and requires `local-address` to return `invalid-state` when the socket hasn't been bound yet. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not bound to any local address. + /// + /// # References + /// - + /// - + /// - + /// - + local-address: func() -> result; + + /// Get the remote address. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + remote-address: func() -> result; + + /// Whether the socket is listening for new connections. + /// + /// Equivalent to the SO_ACCEPTCONN socket option. + is-listening: func() -> bool; + + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + address-family: func() -> ip-address-family; + + /// Whether IPv4 compatibility (dual-stack) mode is disabled or not. + /// + /// Equivalent to the IPV6_V6ONLY socket option. + /// + /// # Typical errors + /// - `invalid-state`: (set) The socket is already bound. + /// - `not-supported`: (get/set) `this` socket is an IPv4 socket. + /// - `not-supported`: (set) Host does not support dual-stack sockets. (Implementations are not required to.) + ipv6-only: func() -> result; + set-ipv6-only: func(value: bool) -> result<_, error-code>; + + /// Hints the desired listen queue size. Implementations are free to ignore this. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// + /// # Typical errors + /// - `not-supported`: (set) The platform does not support changing the backlog size after the initial listen. + /// - `invalid-argument`: (set) The provided value was 0. + /// - `invalid-state`: (set) The socket is already in the Connection state. + set-listen-backlog-size: func(value: u64) -> result<_, error-code>; + + /// Enables or disables keepalive. + /// + /// The keepalive behavior can be adjusted using: + /// - `keep-alive-idle-time` + /// - `keep-alive-interval` + /// - `keep-alive-count` + /// These properties can be configured while `keep-alive-enabled` is false, but only come into effect when `keep-alive-enabled` is true. + /// + /// Equivalent to the SO_KEEPALIVE socket option. + keep-alive-enabled: func() -> result; + set-keep-alive-enabled: func(value: bool) -> result<_, error-code>; + + /// Amount of time the connection has to be idle before TCP starts sending keepalive packets. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// I.e. after setting a value, reading the same setting back may return a different value. + /// + /// Equivalent to the TCP_KEEPIDLE socket option. (TCP_KEEPALIVE on MacOS) + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + keep-alive-idle-time: func() -> result; + set-keep-alive-idle-time: func(value: duration) -> result<_, error-code>; + + /// The time between keepalive packets. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// I.e. after setting a value, reading the same setting back may return a different value. + /// + /// Equivalent to the TCP_KEEPINTVL socket option. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + keep-alive-interval: func() -> result; + set-keep-alive-interval: func(value: duration) -> result<_, error-code>; + + /// The maximum amount of keepalive packets TCP should send before aborting the connection. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// I.e. after setting a value, reading the same setting back may return a different value. + /// + /// Equivalent to the TCP_KEEPCNT socket option. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + keep-alive-count: func() -> result; + set-keep-alive-count: func(value: u32) -> result<_, error-code>; + + /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The TTL value must be 1 or higher. + /// - `invalid-state`: (set) The socket is already in the Connection state. + /// - `invalid-state`: (set) The socket is already in the Listener state. + hop-limit: func() -> result; + set-hop-limit: func(value: u8) -> result<_, error-code>; + + /// The kernel buffer space reserved for sends/receives on this socket. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// I.e. after setting a value, reading the same setting back may return a different value. + /// + /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + /// - `invalid-state`: (set) The socket is already in the Connection state. + /// - `invalid-state`: (set) The socket is already in the Listener state. + receive-buffer-size: func() -> result; + set-receive-buffer-size: func(value: u64) -> result<_, error-code>; + send-buffer-size: func() -> result; + set-send-buffer-size: func(value: u64) -> result<_, error-code>; + + /// Create a `pollable` which will resolve once the socket is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; + + /// Initiate a graceful shutdown. + /// + /// - receive: the socket is not expecting to receive any more data from the peer. All subsequent read + /// operations on the `input-stream` associated with this socket will return an End Of Stream indication. + /// Any data still in the receive queue at time of calling `shutdown` will be discarded. + /// - send: the socket is not expecting to send any more data to the peer. All subsequent write + /// operations on the `output-stream` associated with this socket will return an error. + /// - both: same effect as receive & send combined. + /// + /// The shutdown function does not close (drop) the socket. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not in the Connection state. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code>; + } +} diff --git a/tests/autosar/deps/sockets/udp-create-socket.wit b/tests/autosar/deps/sockets/udp-create-socket.wit new file mode 100644 index 000000000..cc58234d8 --- /dev/null +++ b/tests/autosar/deps/sockets/udp-create-socket.wit @@ -0,0 +1,26 @@ + +interface udp-create-socket { + use network.{network, error-code, ip-address-family}; + use udp.{udp-socket}; + + /// Create a new UDP socket. + /// + /// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX. + /// + /// This function does not require a network capability handle. This is considered to be safe because + /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind` is called, + /// the socket is effectively an in-memory configuration object, unable to communicate with the outside world. + /// + /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. + /// + /// # Typical errors + /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) + /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) + /// + /// # References: + /// - + /// - + /// - + /// - + create-udp-socket: func(address-family: ip-address-family) -> result; +} diff --git a/tests/autosar/deps/sockets/udp.wit b/tests/autosar/deps/sockets/udp.wit new file mode 100644 index 000000000..c8dafadfc --- /dev/null +++ b/tests/autosar/deps/sockets/udp.wit @@ -0,0 +1,277 @@ + +interface udp { + use wasi:io/poll@0.2.0-rc-2023-11-10.{pollable}; + use network.{network, error-code, ip-socket-address, ip-address-family}; + + /// A received datagram. + record incoming-datagram { + /// The payload. + /// + /// Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes. + data: list, + + /// The source address. + /// + /// This field is guaranteed to match the remote address the stream was initialized with, if any. + /// + /// Equivalent to the `src_addr` out parameter of `recvfrom`. + remote-address: ip-socket-address, + } + + /// A datagram to be sent out. + record outgoing-datagram { + /// The payload. + data: list, + + /// The destination address. + /// + /// The requirements on this field depend on how the stream was initialized: + /// - with a remote address: this field must be None or match the stream's remote address exactly. + /// - without a remote address: this field is required. + /// + /// If this value is None, the send operation is equivalent to `send` in POSIX. Otherwise it is equivalent to `sendto`. + remote-address: option, + } + + + + /// A UDP socket handle. + resource udp-socket { + /// Bind the socket to a specific network on the provided IP address and port. + /// + /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which + /// network interface(s) to bind to. + /// If the port is zero, the socket will be bound to a random free port. + /// + /// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. + /// + /// # Typical `start` errors + /// - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) + /// - `invalid-state`: The socket is already bound. (EINVAL) + /// + /// # Typical `finish` errors + /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) + /// - `address-in-use`: Address is already in use. (EADDRINUSE) + /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) + /// - `not-in-progress`: A `bind` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # References + /// - + /// - + /// - + /// - + start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; + finish-bind: func() -> result<_, error-code>; + + /// Set up inbound & outbound communication channels, optionally to a specific peer. + /// + /// This function only changes the local socket configuration and does not generate any network traffic. + /// On success, the `remote-address` of the socket is updated. The `local-address` may be updated as well, + /// based on the best network path to `remote-address`. + /// + /// When a `remote-address` is provided, the returned streams are limited to communicating with that specific peer: + /// - `send` can only be used to send to this destination. + /// - `receive` will only return datagrams sent from the provided `remote-address`. + /// + /// This method may be called multiple times on the same socket to change its association, but + /// only the most recently returned pair of streams will be operational. Implementations may trap if + /// the streams returned by a previous invocation haven't been dropped yet before calling `stream` again. + /// + /// The POSIX equivalent in pseudo-code is: + /// ```text + /// if (was previously connected) { + /// connect(s, AF_UNSPEC) + /// } + /// if (remote_address is Some) { + /// connect(s, remote_address) + /// } + /// ``` + /// + /// Unlike in POSIX, the socket must already be explicitly bound. + /// + /// # Typical errors + /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-argument`: `remote-address` is a non-IPv4-mapped IPv6 address, but the socket was bound to a specific IPv4-mapped IPv6 address. (or vice versa) + /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-state`: The socket is not bound. + /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) + /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// - `connection-refused`: The connection was refused. (ECONNREFUSED) + /// + /// # References + /// - + /// - + /// - + /// - + %stream: func(remote-address: option) -> result, error-code>; + + /// Get the current bound address. + /// + /// POSIX mentions: + /// > If the socket has not been bound to a local name, the value + /// > stored in the object pointed to by `address` is unspecified. + /// + /// WASI is stricter and requires `local-address` to return `invalid-state` when the socket hasn't been bound yet. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not bound to any local address. + /// + /// # References + /// - + /// - + /// - + /// - + local-address: func() -> result; + + /// Get the address the socket is currently streaming to. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not streaming to a specific remote address. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + remote-address: func() -> result; + + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + address-family: func() -> ip-address-family; + + /// Whether IPv4 compatibility (dual-stack) mode is disabled or not. + /// + /// Equivalent to the IPV6_V6ONLY socket option. + /// + /// # Typical errors + /// - `not-supported`: (get/set) `this` socket is an IPv4 socket. + /// - `invalid-state`: (set) The socket is already bound. + /// - `not-supported`: (set) Host does not support dual-stack sockets. (Implementations are not required to.) + ipv6-only: func() -> result; + set-ipv6-only: func(value: bool) -> result<_, error-code>; + + /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The TTL value must be 1 or higher. + unicast-hop-limit: func() -> result; + set-unicast-hop-limit: func(value: u8) -> result<_, error-code>; + + /// The kernel buffer space reserved for sends/receives on this socket. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// I.e. after setting a value, reading the same setting back may return a different value. + /// + /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + receive-buffer-size: func() -> result; + set-receive-buffer-size: func(value: u64) -> result<_, error-code>; + send-buffer-size: func() -> result; + set-send-buffer-size: func(value: u64) -> result<_, error-code>; + + /// Create a `pollable` which will resolve once the socket is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; + } + + resource incoming-datagram-stream { + /// Receive messages on the socket. + /// + /// This function attempts to receive up to `max-results` datagrams on the socket without blocking. + /// The returned list may contain fewer elements than requested, but never more. + /// + /// This function returns successfully with an empty list when either: + /// - `max-results` is 0, or: + /// - `max-results` is greater than 0, but no results are immediately available. + /// This function never returns `error(would-block)`. + /// + /// # Typical errors + /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// - `connection-refused`: The connection was refused. (ECONNREFUSED) + /// + /// # References + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + receive: func(max-results: u64) -> result, error-code>; + + /// Create a `pollable` which will resolve once the stream is ready to receive again. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; + } + + resource outgoing-datagram-stream { + /// Check readiness for sending. This function never blocks. + /// + /// Returns the number of datagrams permitted for the next call to `send`, + /// or an error. Calling `send` with more datagrams than this function has + /// permitted will trap. + /// + /// When this function returns ok(0), the `subscribe` pollable will + /// become ready when this function will report at least ok(1), or an + /// error. + /// + /// Never returns `would-block`. + check-send: func() -> result; + + /// Send messages on the socket. + /// + /// This function attempts to send all provided `datagrams` on the socket without blocking and + /// returns how many messages were actually sent (or queued for sending). This function never + /// returns `error(would-block)`. If none of the datagrams were able to be sent, `ok(0)` is returned. + /// + /// This function semantically behaves the same as iterating the `datagrams` list and sequentially + /// sending each individual datagram until either the end of the list has been reached or the first error occurred. + /// If at least one datagram has been sent successfully, this function never returns an error. + /// + /// If the input list is empty, the function returns `ok(0)`. + /// + /// Each call to `send` must be permitted by a preceding `check-send`. Implementations must trap if + /// either `check-send` was not called or `datagrams` contains more items than `check-send` permitted. + /// + /// # Typical errors + /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-argument`: `remote-address` is a non-IPv4-mapped IPv6 address, but the socket was bound to a specific IPv4-mapped IPv6 address. (or vice versa) + /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-argument`: The socket is in "connected" mode and `remote-address` is `some` value that does not match the address passed to `stream`. (EISCONN) + /// - `invalid-argument`: The socket is not "connected" and no value for `remote-address` was provided. (EDESTADDRREQ) + /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// - `connection-refused`: The connection was refused. (ECONNREFUSED) + /// - `datagram-too-large`: The datagram is too large. (EMSGSIZE) + /// + /// # References + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + send: func(datagrams: list) -> result; + + /// Create a `pollable` which will resolve once the stream is ready to send again. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; + } +} diff --git a/tests/autosar/deps/sockets/world.wit b/tests/autosar/deps/sockets/world.wit new file mode 100644 index 000000000..49ad8d3d9 --- /dev/null +++ b/tests/autosar/deps/sockets/world.wit @@ -0,0 +1,11 @@ +package wasi:sockets@0.2.0-rc-2023-11-10; + +world imports { + import instance-network; + import network; + import udp; + import udp-create-socket; + import tcp; + import tcp-create-socket; + import ip-name-lookup; +} diff --git a/tests/autosar/deps/threads/threads.wit b/tests/autosar/deps/threads/threads.wit new file mode 100644 index 000000000..224339d54 --- /dev/null +++ b/tests/autosar/deps/threads/threads.wit @@ -0,0 +1,16 @@ +interface threads { + /// The result of the `thread-spawn()` function. + /// If spawning the thread was successful, the value is positive + /// and represents a unique thread identifier. Otherwise, the + /// value is negative and it represents error code. + type thread-spawn-result = s32 + + /// A reference to data passed to the start function (`wasi_thread_start()`) called by the newly spawned thread. + type start-arg = u32 + + /// Creates a new thread. + thread-spawn: func( + /// A value being passed to a start function (`wasi_thread_start()`). + start-arg: start-arg, + ) -> thread-spawn-result +} diff --git a/tests/autosar/deps/threads/world.wit b/tests/autosar/deps/threads/world.wit new file mode 100644 index 000000000..9065146c4 --- /dev/null +++ b/tests/autosar/deps/threads/world.wit @@ -0,0 +1,5 @@ +package wasi:threads + +world example-world { + import threads +} diff --git a/tests/autosar/fusion.wit b/tests/autosar/fusion.wit new file mode 100644 index 000000000..e6ddf07ba --- /dev/null +++ b/tests/autosar/fusion.wit @@ -0,0 +1,12 @@ +world fusion { + import radar + export radar-callbacks + + import core + import log + import com + import exec + import wasi:threads/threads + import wasi:io/poll + export wasi:cli/run +} diff --git a/tests/autosar/poll.wit b/tests/autosar/poll.wit new file mode 100644 index 000000000..2b59045d2 --- /dev/null +++ b/tests/autosar/poll.wit @@ -0,0 +1,37 @@ +/// A poll API intended to let users wait for I/O events on multiple handles +/// at once. +interface poll { + /// A "pollable" handle. + /// + /// This is conceptually represents a `stream<_, _>`, or in other words, + /// a stream that one can wait on, repeatedly, but which does not itself + /// produce any data. It's temporary scaffolding until component-model's + /// async features are ready. + /// + /// And at present, it is a `u32` instead of being an actual handle, until + /// the wit-bindgen implementation of handles and resources is ready. + /// + /// `pollable` lifetimes are not automatically managed. Users must ensure + /// that they do not outlive the resource they reference. + /// + /// This [represents a resource](https://github.com/WebAssembly/WASI/blob/main/docs/WitInWasi.md#Resources). + type pollable = u32 + + /// Dispose of the specified `pollable`, after which it may no longer + /// be used. + drop-pollable: func(this: pollable) + + /// Poll for completion on a set of pollables. + /// + /// The "oneoff" in the name refers to the fact that this function must do a + /// linear scan through the entire list of subscriptions, which may be + /// inefficient if the number is large and the same subscriptions are used + /// many times. In the future, this is expected to be obsoleted by the + /// component model async proposal, which will include a scalable waiting + /// facility. + /// + /// The result list is the same length as the argument + /// list, and indicates the readiness of each corresponding + /// element in that / list, with true indicating ready. + poll-oneoff: func(in: list) -> list +} diff --git a/tests/autosar/radar.wit b/tests/autosar/radar.wit new file mode 100644 index 000000000..1d23d88b4 --- /dev/null +++ b/tests/autosar/radar.wit @@ -0,0 +1,135 @@ +// package autosar:fusion + +// TODO figure out whether this is bidirectional +interface com-add { + use com.{instance-identifier} + use com.{profile-check-status} + + resource instance-handle { + get-instance-id: func() -> instance-identifier + clone: func() -> instance-handle + } + + resource sample-ptr { + get: func() -> s32 + get-profile-check-status: func() -> profile-check-status + } + + resource stream-control { + suspend: func() + resume: func() + abort: static func(self: own) + } + + resource abort-handle { + abort: static func(self: own) + } + + resource resume-handle { + resume: static func(self: own) + } +} + +interface radar-types { + record position { + x: s32, + y: s32, + z: s32, + } + record radar-objects { + active: bool, + object-vector: list, + } + enum fusion-variant { + china, usa, europe, russia, + } + + record adjust-output { + success: bool, + effective-position: position, + } + record calibrate-output { + call-result: bool + } +} + +interface radar-consumer { + use types.{error-code} + use radar-types.{adjust-output, calibrate-output} + use com-add.{instance-handle, sample-ptr, resume-handle} + + resource promise-result-adjust-output-error-code { + ready: static func(self: own, value: result) + } + resource promise-result-calibrate-output-error-code { + ready: static func(self: own, value: result) + } + resource promise-u32 { + ready: static func(self: own, value: u32) + } + resource promise-u16 { + ready: static func(self: own, value: u16) + } + + resource stream-instance-handle { + // returns the value if the queue is filled up (backpressure) + ready: func(value: option>, resume: resume-handle) -> option> + } + resource stream-sample-ptr { + ready: func(value: option>, resume: resume-handle) -> option> + } +} + +interface radar-provider { + use types.{error-code} + use core.{instance-specifier} + use com-add.{instance-handle, stream-control, abort-handle} + use radar-consumer.{promise-result-adjust-output-error-code, promise-result-calibrate-output-error-code, stream-instance-handle, stream-sample-ptr, promise-u16, promise-u32} + use radar-types.{position, fusion-variant} + + resource instance { + constructor(handle: borrow) + + adjust: func (promise: promise-result-adjust-output-error-code, target-position: position) -> option + calibrate: func (promise: promise-result-calibrate-output-error-code, configuration: string, radar-variant: fusion-variant) -> option + echo: func (text: string) + + subscribe-brake-event: func (max-sample-count: u32, receiver: stream-sample-ptr) -> result + subscribe-parking-brake-event: func (max-sample-count: u32, receiver: stream-sample-ptr) -> result + + // field + get-update-rate: func(promise: promise-u32) -> option + set-update-rate: func(promise: promise-u32, value: u32) -> option + subscribe-update-rate: func(max-sample-count: u32, receiver: stream-sample-ptr) -> result + subscribe-front-object-distance: func(max-sample-count: u32, receiver: stream-sample-ptr) -> result + get-rear-object-distance: func(promise: promise-u16) -> option + set-object-detection-limit: func(promise: promise-u16, value: u16) -> option + } + + start-find-service: func(spec: borrow, receiver: stream-instance-handle) -> option +} + +// interface service { +// use radar.{instance} +// use core.{instance-specifier} +// use com-add.{abort-handle} +// enum method-call-processing-mode { +// poll, event, event-single-thread, +// } +// // perhaps use own here? +// offer-service: func(service: borrow, spec: borrow, mode: method-call-processing-mode) -> abort-handle +// //stop-offer-service: func (service: borrow) +// } + +world radar-world { + export radar-provider + import radar-consumer + + import core + import log + import com + import exec + // import service + import wasi:threads/threads + export wasi:cli/run +} diff --git a/tests/autosar/wit b/tests/autosar/wit new file mode 120000 index 000000000..945c9b46d --- /dev/null +++ b/tests/autosar/wit @@ -0,0 +1 @@ +. \ No newline at end of file diff --git a/tests/codegen/trivial-lists.wit b/tests/codegen/trivial-lists.wit new file mode 100644 index 000000000..d60707043 --- /dev/null +++ b/tests/codegen/trivial-lists.wit @@ -0,0 +1,13 @@ +package foo:foo; + +// a subset of simple-lists +interface trivial-lists { + trivial-list1: func(l: list); + trivial-list2: func() -> list; + trivial-list3: func(a: list, b: list) -> list; +} + +world my-world { + import trivial-lists; + export trivial-lists; +} diff --git a/tests/runtime-async/async/future-string/runner.cpp b/tests/runtime-async/async/future-string/runner.cpp new file mode 100644 index 000000000..12cfe52bb --- /dev/null +++ b/tests/runtime-async/async/future-string/runner.cpp @@ -0,0 +1,14 @@ +#include + +void assert_str(wit::string const& str, const char* expected) { + size_t expected_len = strlen(expected); + assert(str.size() == expected_len); + assert(memcmp(str.data(), expected, expected_len) == 0); +} + +int main() { + std::future f = a::b::the_test::F(); + wit::string s = f.get(); + assert_str(s, "Hello"); + return 0; +} diff --git a/tests/runtime-async/async/future-string/runner.rs b/tests/runtime-async/async/future-string/runner.rs new file mode 100644 index 000000000..99f1ccba1 --- /dev/null +++ b/tests/runtime-async/async/future-string/runner.rs @@ -0,0 +1,12 @@ +include!(env!("BINDINGS")); + +use wit_bindgen::rt::async_support; + +use crate::a::b::the_test::f; + +fn main() { + async_support::block_on(async { + let result = f().await; + assert_eq!(result, String::from("Hello")); + }); +} diff --git a/tests/runtime-async/async/future-string/test.cpp b/tests/runtime-async/async/future-string/test.cpp new file mode 100644 index 000000000..6f36de99a --- /dev/null +++ b/tests/runtime-async/async/future-string/test.cpp @@ -0,0 +1,7 @@ +# include + +std::future exports::a::b::the_test::F() { + std::promise result; + result.set_value(wit::string::from_view("Hello")); + return result.get_future(); +} diff --git a/tests/runtime-async/async/future-string/test.rs b/tests/runtime-async/async/future-string/test.rs new file mode 100644 index 000000000..bdb01261f --- /dev/null +++ b/tests/runtime-async/async/future-string/test.rs @@ -0,0 +1,19 @@ +include!(env!("BINDINGS")); + +struct Component; + +export!(Component); + +use crate::exports::a::b::the_test::Guest; + +use wit_bindgen::rt::async_support::{self, FutureReader}; + +impl Guest for Component { + fn f() -> FutureReader { + let (wr, rd) = wit_future::new(String::default); + async_support::spawn(async move { + wr.write(String::from("Hello")).await.unwrap(); + }); + rd + } +} diff --git a/tests/runtime-async/async/future-string/test.wit b/tests/runtime-async/async/future-string/test.wit new file mode 100644 index 000000000..174f61646 --- /dev/null +++ b/tests/runtime-async/async/future-string/test.wit @@ -0,0 +1,12 @@ +package a:b; + +interface the-test { + f: func() -> future; +} + +world test { + export the-test; +} +world runner { + import the-test; +} diff --git a/tests/runtime-async/async/stream-string/runner.cpp b/tests/runtime-async/async/stream-string/runner.cpp new file mode 100644 index 000000000..3aaad209f --- /dev/null +++ b/tests/runtime-async/async/stream-string/runner.cpp @@ -0,0 +1,30 @@ +#include +#include "module_cpp.h" +#include + +static constexpr uint32_t SIZE = 5; +static const char *(expected)[SIZE] = { + "Hello", "World!", "From", "a", "stream." +}; +static uint32_t next = 0; + +static bool equal(wit::string const& a, char const* b) { + if (a.size()!=strlen(b)) return false; + return !memcmp(a.data(), b, a.size()); +} + +int main() { + wit::stream stream = a::b::the_test::F(); + stream.buffering(1); + + std::move(stream).set_reader([](wit::span data) { + if (data.size() > 0) { + assert(data.size()==1); + assert(next +#include +#include + +static constexpr uint32_t SIZE = 5; +static const char *(pattern)[SIZE] = { + "Hello", "World!", "From", "a", "stream." +}; +static uint32_t next = 0; + +static symmetric::runtime::symmetric_executor::CallbackState ready_to_write(stream_writer* data) { + if (nextwrite(std::vector(1, wit::string::from_view(pattern[next]))); + ++next; + return symmetric::runtime::symmetric_executor::CallbackState::kPending; + } else { + data->write(std::vector()); + auto release = std::unique_ptr>(data); + return symmetric::runtime::symmetric_executor::CallbackState::kReady; + } +} + +wit::stream exports::a::b::the_test::F() { + auto streampair = create_wasi_stream(); +#if 0 + stream_writer* streampointer = std::make_unique>(std::move(std::move(streampair).first)).release(); + + std::async(std::launch::async, [streampointer](){ + streampointer->write(std::vector(1, wit::string::from_view("Hello"))); + streampointer->write(std::vector(1, wit::string::from_view("Hello"))); + streampointer->write(std::vector(1, wit::string::from_view("Hello"))); + streampointer->write(std::vector(1, wit::string::from_view("Hello"))); + streampointer->write(std::vector(1, wit::string::from_view("Hello"))); + auto release = std::unique_ptr>(streampointer); + }); + // TODO: How to handle the returned future +#elif 1 + stream_writer* streampointer = std::make_unique>(std::move(std::move(streampair).first)).release(); + // manual handling for now + symmetric::runtime::symmetric_executor::Register(streampointer->handle.WriteReadySubscribe(), + symmetric::runtime::symmetric_executor::CallbackFunction(wit::ResourceImportBase{(wit::ResourceImportBase::handle_t)&ready_to_write}), + symmetric::runtime::symmetric_executor::CallbackData(wit::ResourceImportBase{(wit::ResourceImportBase::handle_t)streampointer}) + ); +#else + std::vector feed; + feed.push_back(wit::string::from_view("Hello")); + feed.push_back(wit::string::from_view("World!")); + feed.push_back(wit::string::from_view("From")); + feed.push_back(wit::string::from_view("a")); + feed.push_back(wit::string::from_view("stream.")); + streampair.first.write(std::move(feed)); + // TODO: Blocking doesn't work well in this test +#endif + return std::move(streampair).second; +} diff --git a/tests/runtime-async/async/stream-string/test.rs b/tests/runtime-async/async/stream-string/test.rs new file mode 100644 index 000000000..ec362c1f2 --- /dev/null +++ b/tests/runtime-async/async/stream-string/test.rs @@ -0,0 +1,23 @@ +include!(env!("BINDINGS")); + +struct Component; + +export!(Component); + +use crate::exports::a::b::the_test::Guest; + +use wit_bindgen::rt::async_support::{self, StreamReader}; + +impl Guest for Component { + fn f() -> StreamReader { + let (mut wr, rd) = wit_stream::new(); + async_support::spawn(async move { + wr.write(vec![String::from("Hello")]).await; + wr.write(vec![String::from("World!")]).await; + wr.write(vec![String::from("From")]).await; + wr.write(vec![String::from("a")]).await; + wr.write(vec![String::from("stream.")]).await; + }); + rd + } +} diff --git a/tests/runtime-async/async/stream-string/test.wit b/tests/runtime-async/async/stream-string/test.wit new file mode 100644 index 000000000..bf3ab480b --- /dev/null +++ b/tests/runtime-async/async/stream-string/test.wit @@ -0,0 +1,12 @@ +package a:b; + +interface the-test { + f: func() -> stream; +} + +world test { + export the-test; +} +world runner { + import the-test; +} diff --git a/tests/runtime/lists/wasm.new.cpp b/tests/runtime/lists/wasm.new.cpp new file mode 100644 index 000000000..0e4481a6d --- /dev/null +++ b/tests/runtime/lists/wasm.new.cpp @@ -0,0 +1,185 @@ +#include +#include +#include +#include + +uint32_t exports::lists::AllocatedBytes() { + return 0; +} + +static bool equal(wit::string const&a, std::string_view b) { + return a.get_view() == b; +} +static bool equal(wit::string const&a, const char x[]) { + return a.get_view() == x; +} +template +static bool equal(T const&a, S const& b) { + return a == b; +} +template +static bool equal(wit::span const&a, wit::span const& b) { + if (a.size() != b.size()) { return false; } + for (uint32_t i = 0; i +static bool equal(wit::vector const&a, wit::span const& b) { + return equal(a.get_view(), b); +} +template +static bool equal(wit::span const&a, wit::vector const& b) { + return equal(b, a); +} +template +static bool equal(wit::span const&a, std::vector const& b) { + return equal(a, wit::span(b)); +} +template +static bool equal(wit::vector const&a, std::vector const& b) { + return equal(a.get_view(), wit::span(b)); +} +static bool equal(wit::vector const&a, std::vector const& b) { + return equal(a.get_view(), wit::span(b)); +} +template +static bool equal(std::tuple const&a, std::tuple const& b) { + return equal(std::get<0>(a), std::get<0>(b)) && equal(std::get<1>(a), std::get<1>(b)); +} + +void exports::lists::TestImports() { + //let _guard = testRust_wasm::guard(); + + test::lists::test::EmptyListParam(wit::span(std::vector())); + test::lists::test::EmptyStringParam(""); + assert(test::lists::test::EmptyListResult().empty()); + assert(test::lists::test::EmptyStringResult().empty()); + + test::lists::test::ListParam(std::vector{1, 2, 3, 4}); + test::lists::test::ListParam2("foo"); + test::lists::test::ListParam3(std::vector{"foo", "bar", "baz"}); + test::lists::test::ListParam4(std::vector>{ + std::vector{"foo", "bar"}, + std::vector{"baz"}, + }); + assert(equal(test::lists::test::ListResult(), std::vector{1, 2, 3, 4, 5})); + assert(equal(test::lists::test::ListResult2(), "hello!")); + assert(equal(test::lists::test::ListResult3(), std::vector{"hello,", "world!"})); + + assert(equal(test::lists::test::ListRoundtrip(wit::span(std::vector())), std::vector())); + assert(equal(test::lists::test::ListRoundtrip(wit::span(std::vector{'x'})), std::vector{'x'})); + assert(equal(test::lists::test::ListRoundtrip(wit::span(std::vector{'h', 'e', 'l', 'l', 'o'})), std::vector{'h', 'e', 'l', 'l', 'o'})); + + assert(equal(test::lists::test::StringRoundtrip("x"), "x")); + assert(equal(test::lists::test::StringRoundtrip(""), "")); + assert(equal(test::lists::test::StringRoundtrip("hello"), "hello")); + assert(equal(test::lists::test::StringRoundtrip("hello ⚑ world"), "hello ⚑ world")); + + assert(equal( + test::lists::test::ListMinmax8(std::vector{0, UINT8_MAX}, std::vector{INT8_MIN, INT8_MAX}), + std::make_tuple(std::vector{0, UINT8_MAX}, std::vector{INT8_MIN, INT8_MAX}) + )); + assert(equal( + test::lists::test::ListMinmax16(std::vector{0, UINT16_MAX}, std::vector{INT16_MIN, INT16_MAX}), + std::make_tuple(std::vector{0, UINT16_MAX}, std::vector{INT16_MIN, INT16_MAX}) + )); + assert(equal( + test::lists::test::ListMinmax32(std::vector{0, UINT32_MAX}, std::vector{INT32_MIN, INT32_MAX}), + std::make_tuple(std::vector{0, UINT32_MAX}, std::vector{INT32_MIN, INT32_MAX}) + )); + assert(equal( + test::lists::test::ListMinmax64(std::vector{0, UINT64_MAX}, std::vector{INT64_MIN, INT64_MAX}), + std::make_tuple(std::vector{0, UINT64_MAX}, std::vector{INT64_MIN, INT64_MAX}) + )); + assert(equal( + test::lists::test::ListMinmaxFloat( + std::vector{FLT_MIN, FLT_MAX, -HUGE_VALF, HUGE_VALF}, + std::vector{DBL_MIN, DBL_MAX, -HUGE_VAL, HUGE_VAL} + ), + std::make_tuple( + std::vector{FLT_MIN, FLT_MAX, -HUGE_VALF, HUGE_VALF}, + std::vector{DBL_MIN, DBL_MAX, -HUGE_VAL, HUGE_VAL} + ) + )); +} + + +void exports::test::lists::test::EmptyListParam(wit::span a) { + assert(a.empty()); +} + +void exports::test::lists::test::EmptyStringParam(std::string_view a) { + assert(a.empty()); +} + +wit::vector exports::test::lists::test::EmptyListResult() { + return wit::vector(); +} + +wit::string exports::test::lists::test::EmptyStringResult() { + return wit::string::from_view(std::string_view()); +} + +void exports::test::lists::test::ListParam(wit::span list) { + assert(equal(list, std::vector{1, 2, 3, 4})); +} + +void exports::test::lists::test::ListParam2(std::string_view ptr) { + assert(equal(ptr, std::string_view("foo"))); +} + +void exports::test::lists::test::ListParam3(wit::span ptr) { + assert(equal(ptr.size(), size_t(3))); + assert(equal(ptr[0], std::string_view("foo"))); + assert(equal(ptr[1], std::string_view("bar"))); + assert(equal(ptr[2], std::string_view("baz"))); +} + +void exports::test::lists::test::ListParam4(wit::span> ptr) { + assert(equal(ptr.size(), size_t(2))); + assert(equal(ptr[0][0], std::string_view("foo"))); + assert(equal(ptr[0][1], std::string_view("bar"))); + assert(equal(ptr[1][0], std::string_view("baz"))); +} + +wit::vector exports::test::lists::test::ListResult() { + return wit::vector::from_view(wit::span(std::vector{1, 2, 3, 4, 5})); +} + +wit::string exports::test::lists::test::ListResult2() { + return wit::string::from_view("hello!"); +} + +wit::vector exports::test::lists::test::ListResult3() { + return wit::vector::from_view(wit::span(std::vector{wit::string::from_view("hello,"), wit::string::from_view("world!")})); +} + +wit::vector exports::test::lists::test::ListRoundtrip(wit::span x) { + return wit::vector::from_view(x); +} + +wit::string exports::test::lists::test::StringRoundtrip(std::string_view x) { + return wit::string::from_view(x); +} + +std::tuple, wit::vector> exports::test::lists::test::ListMinmax8(wit::span a, wit::span b) { + return std::make_tuple(wit::vector::from_view(a), wit::vector::from_view(b)); +} + +std::tuple, wit::vector> exports::test::lists::test::ListMinmax16(wit::span a, wit::span b) { + return std::make_tuple(wit::vector::from_view(a), wit::vector::from_view(b)); +} + +std::tuple, wit::vector> exports::test::lists::test::ListMinmax32(wit::span a, wit::span b) { + return std::make_tuple(wit::vector::from_view(a), wit::vector::from_view(b)); +} + +std::tuple, wit::vector> exports::test::lists::test::ListMinmax64(wit::span a, wit::span b) { + return std::make_tuple(wit::vector::from_view(a), wit::vector::from_view(b)); +} + +std::tuple, wit::vector> exports::test::lists::test::ListMinmaxFloat(wit::span a, wit::span b) { + return std::make_tuple(wit::vector::from_view(a), wit::vector::from_view(b)); +} diff --git a/tests/runtime/many-arguments/wasm.new.cpp b/tests/runtime/many-arguments/wasm.new.cpp new file mode 100644 index 000000000..80b70603e --- /dev/null +++ b/tests/runtime/many-arguments/wasm.new.cpp @@ -0,0 +1,46 @@ +#include +#include + +template +bool equal(T const&a, T const&b) { + return a==b; +} + +void exports::many_arguments::ManyArguments( + uint64_t a1, + uint64_t a2, + uint64_t a3, + uint64_t a4, + uint64_t a5, + uint64_t a6, + uint64_t a7, + uint64_t a8, + uint64_t a9, + uint64_t a10, + uint64_t a11, + uint64_t a12, + uint64_t a13, + uint64_t a14, + uint64_t a15, + uint64_t a16 +) { + assert(equal(a1, (uint64_t)1)); + assert(equal(a2, (uint64_t)2)); + assert(equal(a3, (uint64_t)3)); + assert(equal(a4, (uint64_t)4)); + assert(equal(a5, (uint64_t)5)); + assert(equal(a6, (uint64_t)6)); + assert(equal(a7, (uint64_t)7)); + assert(equal(a8, (uint64_t)8)); + assert(equal(a9, (uint64_t)9)); + assert(equal(a10, (uint64_t)10)); + assert(equal(a11, (uint64_t)11)); + assert(equal(a12, (uint64_t)12)); + assert(equal(a13, (uint64_t)13)); + assert(equal(a14, (uint64_t)14)); + assert(equal(a15, (uint64_t)15)); + assert(equal(a16, (uint64_t)16)); + ::test::many_arguments::ManyArguments( + a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16 + ); +} diff --git a/tests/runtime/numbers/runner.cpp b/tests/runtime/numbers/runner.cpp index 85aaf38a6..89e661ade 100644 --- a/tests/runtime/numbers/runner.cpp +++ b/tests/runtime/numbers/runner.cpp @@ -1,5 +1,6 @@ #include #include +#include #include int main() diff --git a/tests/runtime/numbers/wasm.cpp b/tests/runtime/numbers/wasm.cpp new file mode 100644 index 000000000..4da3f1ce9 --- /dev/null +++ b/tests/runtime/numbers/wasm.cpp @@ -0,0 +1,112 @@ +#include +#include +#include +#include + +uint8_t exports::test::numbers::test::RoundtripU8(uint8_t a) { + return a; +} + +int8_t exports::test::numbers::test::RoundtripS8(int8_t a) { + return a; +} + +uint16_t exports::test::numbers::test::RoundtripU16(uint16_t a) { + return a; +} + +int16_t exports::test::numbers::test::RoundtripS16(int16_t a) { + return a; +} + +uint32_t exports::test::numbers::test::RoundtripU32(uint32_t a) { + return a; +} + +int32_t exports::test::numbers::test::RoundtripS32(int32_t a) { + return a; +} + +uint64_t exports::test::numbers::test::RoundtripU64(uint64_t a) { + return a; +} + +int64_t exports::test::numbers::test::RoundtripS64(int64_t a) { + return a; +} + +float exports::test::numbers::test::RoundtripF32(float a) { + return a; +} + +double exports::test::numbers::test::RoundtripF64(double a) { + return a; +} + +uint32_t exports::test::numbers::test::RoundtripChar(uint32_t a) { + return a; +} + +static uint32_t SCALAR = 0; + +void exports::test::numbers::test::SetScalar(uint32_t a) { + SCALAR = a; +} + +uint32_t exports::test::numbers::test::GetScalar(void) { + return SCALAR; +} + + +void exports::numbers::TestImports() { + assert(::test::numbers::test::RoundtripU8(1) == 1); + assert(::test::numbers::test::RoundtripU8(0) == 0); + assert(::test::numbers::test::RoundtripU8(UCHAR_MAX) == UCHAR_MAX); + + assert(::test::numbers::test::RoundtripS8(1) == 1); + assert(::test::numbers::test::RoundtripS8(SCHAR_MIN) == SCHAR_MIN); + assert(::test::numbers::test::RoundtripS8(SCHAR_MAX) == SCHAR_MAX); + + assert(::test::numbers::test::RoundtripU16(1) == 1); + assert(::test::numbers::test::RoundtripU16(0) == 0); + assert(::test::numbers::test::RoundtripU16(USHRT_MAX) == USHRT_MAX); + + assert(::test::numbers::test::RoundtripS16(1) == 1); + assert(::test::numbers::test::RoundtripS16(SHRT_MIN) == SHRT_MIN); + assert(::test::numbers::test::RoundtripS16(SHRT_MAX) == SHRT_MAX); + + assert(::test::numbers::test::RoundtripU32(1) == 1); + assert(::test::numbers::test::RoundtripU32(0) == 0); + assert(::test::numbers::test::RoundtripU32(UINT_MAX) == UINT_MAX); + + assert(::test::numbers::test::RoundtripS32(1) == 1); + assert(::test::numbers::test::RoundtripS32(INT_MIN) == INT_MIN); + assert(::test::numbers::test::RoundtripS32(INT_MAX) == INT_MAX); + + assert(::test::numbers::test::RoundtripU64(1) == 1); + assert(::test::numbers::test::RoundtripU64(0) == 0); + assert(::test::numbers::test::RoundtripU64(ULONG_MAX) == ULONG_MAX); + + assert(::test::numbers::test::RoundtripS64(1) == 1); + assert(::test::numbers::test::RoundtripS64(LONG_MIN) == LONG_MIN); + assert(::test::numbers::test::RoundtripS64(LONG_MAX) == LONG_MAX); + + assert(::test::numbers::test::RoundtripF32(1.0) == 1.0); + assert(::test::numbers::test::RoundtripF32(INFINITY) == INFINITY); + assert(::test::numbers::test::RoundtripF32(-INFINITY) == -INFINITY); + assert(isnan(::test::numbers::test::RoundtripF32(NAN))); + + assert(::test::numbers::test::RoundtripF64(1.0) == 1.0); + assert(::test::numbers::test::RoundtripF64(INFINITY) == INFINITY); + assert(::test::numbers::test::RoundtripF64(-INFINITY) == -INFINITY); + assert(isnan(::test::numbers::test::RoundtripF64(NAN))); + + assert(::test::numbers::test::RoundtripChar('a') == 'a'); + assert(::test::numbers::test::RoundtripChar(' ') == ' '); + assert(::test::numbers::test::RoundtripChar(U'🚩') == U'🚩'); + + ::test::numbers::test::SetScalar(2); + assert(::test::numbers::test::GetScalar() == 2); + ::test::numbers::test::SetScalar(4); + assert(::test::numbers::test::GetScalar() == 4); +} diff --git a/tests/runtime/options/wasm.new.cpp b/tests/runtime/options/wasm.new.cpp new file mode 100644 index 000000000..8925a3f74 --- /dev/null +++ b/tests/runtime/options/wasm.new.cpp @@ -0,0 +1,58 @@ +#include +#include +#include +#include + +template +static bool equal(T const& a, T const& b) { + return a==b; +} +static bool equal(wit::string const& a, std::string const& b) { + return a.get_view() == std::string_view(b); +} +static bool equal(std::optional const& a, std::optional const& b) { + if (a.has_value() != b.has_value()) return false; + if (a.has_value()) { + return equal(a.value(), b.value()); + } + return true; +} + +void exports::options::TestImports() { + using namespace test::options::test; + + OptionNoneParam(std::optional()); + OptionSomeParam(std::optional("foo")); + assert(!OptionNoneResult()); + assert(equal(OptionSomeResult(), std::optional("foo"))); + assert(equal(OptionRoundtrip(std::optional("foo")), std::optional("foo"))); + assert(equal(DoubleOptionRoundtrip(std::optional>(std::optional(42))), std::optional>(std::optional(42)))); + assert(equal(DoubleOptionRoundtrip(std::optional>(std::optional())), std::optional>(std::optional()))); + assert(equal(DoubleOptionRoundtrip(std::optional>()), std::optional>())); +} + +void exports::test::options::test::OptionNoneParam(std::optional a) +{ + assert(!a.has_value()); +} + +std::optional exports::test::options::test::OptionNoneResult() { + return std::optional(); +} + +void exports::test::options::test::OptionSomeParam(std::optional a) { + assert(equal(a, std::optional("foo"))); +} + +std::optional exports::test::options::test::OptionSomeResult() { + return std::optional(wit::string::from_view("foo")); +} + +std::optional exports::test::options::test::OptionRoundtrip(std::optional a) { + if (!a.has_value()) return std::optional(); + return std::optional(wit::string::from_view(*a)); +} + +std::optional> exports::test::options::test::DoubleOptionRoundtrip(std::optional> a) { + return a; +} diff --git a/tests/runtime/records/wasm.new.cpp b/tests/runtime/records/wasm.new.cpp new file mode 100644 index 000000000..d5b276f46 --- /dev/null +++ b/tests/runtime/records/wasm.new.cpp @@ -0,0 +1,75 @@ +#include +#include + +template +bool equal(T const&a, T const&b) { + return a==b; +} + +void exports::records::TestImports() { + using namespace ::test::records::test; + + assert(equal(MultipleResults(), std::tuple(4, 5))); + + assert(equal(SwapTuple(std::tuple(1, 2)), std::tuple(2, 1))); + assert(equal(RoundtripFlags1(::test::records::test::F1::kA), ::test::records::test::F1::kA)); + assert(equal(RoundtripFlags1(::test::records::test::F1::k_None), ::test::records::test::F1::k_None)); + assert(equal(RoundtripFlags1(::test::records::test::F1::kB), ::test::records::test::F1::kB)); + assert(equal(RoundtripFlags1(::test::records::test::F1::kA | ::test::records::test::F1::kB), ::test::records::test::F1::kA | ::test::records::test::F1::kB)); + + assert(equal(RoundtripFlags2(::test::records::test::F2::kC), ::test::records::test::F2::kC)); + assert(equal(RoundtripFlags2(::test::records::test::F2::k_None), ::test::records::test::F2::k_None)); + assert(equal(RoundtripFlags2(::test::records::test::F2::kD), ::test::records::test::F2::kD)); + assert(equal(RoundtripFlags2(::test::records::test::F2::kC | ::test::records::test::F2::kE), ::test::records::test::F2::kC | ::test::records::test::F2::kE)); + + assert(equal( + RoundtripFlags3(::test::records::test::Flag8::kB0, ::test::records::test::Flag16::kB1, ::test::records::test::Flag32::kB2), + std::tuple<::test::records::test::Flag8, ::test::records::test::Flag16, ::test::records::test::Flag32>(::test::records::test::Flag8::kB0, ::test::records::test::Flag16::kB1, ::test::records::test::Flag32::kB2) + )); + + { + auto r = RoundtripRecord1(::test::records::test::R1 { + 8, + ::test::records::test::F1::k_None, + }); + assert(equal(r.a, (uint8_t)8)); + assert(equal(r.b, ::test::records::test::F1::k_None)); + } + + auto r = RoundtripRecord1(::test::records::test::R1 { + 0, + ::test::records::test::F1::kA | ::test::records::test::F1::kB, + }); + assert(equal(r.a, (uint8_t)0)); + assert(equal(r.b, ::test::records::test::F1::kA | ::test::records::test::F1::kB)); + + assert(equal(Tuple1(std::tuple(1)), std::tuple(1))); +} + +std::tuple exports::test::records::test::MultipleResults() { + return std::tuple(100, 200); +} + +std::tuple exports::test::records::test::SwapTuple(std::tuple a) { + return std::tuple(std::get<1>(a), std::get<0>(a)); +} + +test::records::test::F1 exports::test::records::test::RoundtripFlags1(::test::records::test::F1 a) { + return a; +} + +test::records::test::F2 exports::test::records::test::RoundtripFlags2(::test::records::test::F2 a) { + return a; +} + +std::tuple exports::test::records::test::RoundtripFlags3(::test::records::test::Flag8 a, ::test::records::test::Flag16 b, ::test::records::test::Flag32 c) { + return std::tuple<::test::records::test::Flag8, ::test::records::test::Flag16, ::test::records::test::Flag32>(a, b, c); +} + +test::records::test::R1 exports::test::records::test::RoundtripRecord1(::test::records::test::R1 a) { + return a; +} + +std::tuple exports::test::records::test::Tuple1(std::tuple a) { + return std::tuple(std::get<0>(a)); +} diff --git a/tests/runtime/resource_floats/intermediate.rs b/tests/runtime/resource_floats/intermediate.rs index 31bb862b0..c18fae6ef 100644 --- a/tests/runtime/resource_floats/intermediate.rs +++ b/tests/runtime/resource_floats/intermediate.rs @@ -1,3 +1,5 @@ +//@ args = '--link-name leaf-rust' + include!(env!("BINDINGS")); use exports::exports::{Float as FloatExport, GuestFloat}; diff --git a/tests/runtime/resource_floats/runner.rs b/tests/runtime/resource_floats/runner.rs index a41586aa5..a6b57f97e 100644 --- a/tests/runtime/resource_floats/runner.rs +++ b/tests/runtime/resource_floats/runner.rs @@ -1,3 +1,5 @@ +//@ args = '--link-name intermediate-rust' + include!(env!("BINDINGS")); use exports::Float as Float2; diff --git a/tests/runtime/results/wasm.new.cpp b/tests/runtime/results/wasm.new.cpp new file mode 100644 index 000000000..68ea1e46a --- /dev/null +++ b/tests/runtime/results/wasm.new.cpp @@ -0,0 +1,56 @@ +#include +#include + +template +bool equal(T const&a, T const&b) { + return a==b; +} + +std::expected exports::test::results::test::StringError(float a) { + return ::test::results::test::StringError(a); +} + +std::expected exports::test::results::test::EnumError(float a) { + auto result = ::test::results::test::EnumError(a); + if (result.has_value()) { return result.value(); } + return std::unexpected(result.error()); + // if (result.error()==::test::results::test::E::kA) { return std::unexpected(::test::results::test::E::kA); } + // if (result.error()==::test::results::test::E::kB) { return std::unexpected(::test::results::test::E::kB); } + // if (result.error()==::test::results::test::E::kC) { return std::unexpected(::test::results::test::E::kC); } +} + +std::expected exports::test::results::test::RecordError(float a) { + auto result = ::test::results::test::RecordError(a); + if (result.has_value()) { return result.value(); } + return std::unexpected(::test::results::test::E2{ result.error().line, result.error().column }); +} + +std::expected exports::test::results::test::VariantError(float a) { + auto result = ::test::results::test::VariantError(a); + if (result.has_value()) { return result.value(); } + return std::unexpected(result.error()); + + // match test_imports::variant_error(a) { + // Ok(b) => Ok(b), + // Err(test_imports::E3::E1(test_imports::E::A)) => { + // Err(test_exports::E3::E1(test_exports::E::A)) + // } + // Err(test_imports::E3::E1(test_imports::E::B)) => { + // Err(test_exports::E3::E1(test_exports::E::B)) + // } + // Err(test_imports::E3::E1(test_imports::E::C)) => { + // Err(test_exports::E3::E1(test_exports::E::C)) + // } + // Err(test_imports::E3::E2(test_imports::E2 { line, column })) => { + // Err(test_exports::E3::E2(test_exports::E2 { line, column })) + // } + // } +} + +std::expected exports::test::results::test::EmptyError(uint32_t a) { + return ::test::results::test::EmptyError(a); +} + +std::expected, wit::string> exports::test::results::test::DoubleError(uint32_t a) { + return ::test::results::test::DoubleError(a); +} diff --git a/tests/runtime/smoke/wasm.cpp b/tests/runtime/smoke/wasm.cpp new file mode 100644 index 000000000..a850741e3 --- /dev/null +++ b/tests/runtime/smoke/wasm.cpp @@ -0,0 +1,6 @@ +#include +//#include + +void exports::smoke::Thunk() { + test::smoke::imports::Thunk(); +} diff --git a/tests/runtime/strings/wasm.cpp b/tests/runtime/strings/wasm.cpp new file mode 100644 index 000000000..6ab504089 --- /dev/null +++ b/tests/runtime/strings/wasm.cpp @@ -0,0 +1,28 @@ +#include +#include +#include +#include +#include + +void assert_str(std::string_view str, const char* expected) { + size_t expected_len = strlen(expected); + assert(str.size() == expected_len); + assert(memcmp(str.data(), expected, expected_len) == 0); +} + +void exports::strings::TestImports() { + test::strings::imports::TakeBasic(std::string_view("latin utf16")); + + wit::string str2 = test::strings::imports::ReturnUnicode(); + assert_str(str2.get_view(), "🚀🚀🚀 𠈄𓀀"); +} + +wit::string exports::strings::ReturnEmpty() { + // return a non-zero address (follows cabi_realloc logic) + return wit::string((char const*)1, 0); +} + +wit::string exports::strings::Roundtrip(wit::string &&str) { + assert(str.size() > 0); + return std::move(str); +} diff --git a/tests/runtime/strings/wasm.new.cpp b/tests/runtime/strings/wasm.new.cpp new file mode 100644 index 000000000..6988f224d --- /dev/null +++ b/tests/runtime/strings/wasm.new.cpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include + +void assert_str(std::string_view str, const char* expected) { + size_t expected_len = strlen(expected); + assert(str.size() == expected_len); + assert(memcmp(str.data(), expected, expected_len) == 0); +} + +void exports::strings::TestImports() { + test::strings::imports::TakeBasic(std::string_view("latin utf16")); + + wit::string str2 = test::strings::imports::ReturnUnicode(); + assert_str(str2.get_view(), "🚀🚀🚀 𠈄𓀀"); +} + +wit::string exports::strings::ReturnEmpty() { + // return a non-zero address (follows cabi_realloc logic) + return wit::string((char const*)1, 0); +} + +// new API: Identical for guest import and export +wit::string exports::strings::Roundtrip(std::string_view str) { + assert(str.size() > 0); + return wit::string::from_view(str); +} diff --git a/tests/runtime/strings/wasm.rs b/tests/runtime/strings/wasm.rs new file mode 100644 index 000000000..9fe6d4c9b --- /dev/null +++ b/tests/runtime/strings/wasm.rs @@ -0,0 +1,25 @@ +wit_bindgen::generate!({ + path: "../../tests/runtime/strings", +}); + +struct Exports; + +export!(Exports); + +impl Guest for Exports { + fn test_imports() -> () { + test::strings::imports::take_basic("latin utf16"); + + let str2 = test::strings::imports::return_unicode(); + assert_eq!(str2, "🚀🚀🚀 𠈄𓀀"); + } + + fn return_empty() -> String { + Default::default() + } + + fn roundtrip(s: String) -> String { + assert!(!s.is_empty()); + s + } +} diff --git a/wasm-tools.patch b/wasm-tools.patch new file mode 100644 index 000000000..d2cabf2c6 --- /dev/null +++ b/wasm-tools.patch @@ -0,0 +1,15 @@ +diff --git a/crates/wit-parser/src/abi.rs b/crates/wit-parser/src/abi.rs +index 1c383384..e5cbb2c1 100644 +--- a/crates/wit-parser/src/abi.rs ++++ b/crates/wit-parser/src/abi.rs +@@ -148,6 +148,10 @@ impl Resolve { + params.truncate(0); + params.push(WasmType::Pointer); + indirect_params = true; ++ } else { ++ if matches!((&func.kind,variant), (crate::FunctionKind::Method(_),AbiVariant::GuestExport)) { ++ params.get_mut(0).map(|p| *p=WasmType::Pointer); ++ } + } + + let mut results = Vec::new();