diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index ee5a63b30..7e9f53cfa 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -14,6 +14,7 @@ mod path; pub use path::name_package_module; mod async_; pub use async_::AsyncFilterSet; +pub mod symbol_name; #[derive(Default, Copy, Clone, PartialEq, Eq, Debug)] pub enum Direction { diff --git a/crates/cpp/src/symbol_name.rs b/crates/core/src/symbol_name.rs similarity index 98% rename from crates/cpp/src/symbol_name.rs rename to crates/core/src/symbol_name.rs index 4b71d1005..1798eb749 100644 --- a/crates/cpp/src/symbol_name.rs +++ b/crates/core/src/symbol_name.rs @@ -1,4 +1,4 @@ -use wit_bindgen_core::abi; +use crate::abi; fn hexdigit(v: u32) -> char { if v < 10 { diff --git a/crates/cpp/src/lib.rs b/crates/cpp/src/lib.rs index 2d7b30099..e0277893e 100644 --- a/crates/cpp/src/lib.rs +++ b/crates/cpp/src/lib.rs @@ -9,12 +9,13 @@ use std::{ process::{Command, Stdio}, str::FromStr, }; -use symbol_name::{make_external_component, make_external_symbol}; use wit_bindgen_c::to_c_ident; use wit_bindgen_core::{ Files, InterfaceGenerator, Source, Types, WorldGenerator, abi::{self, AbiVariant, Bindgen, Bitcast, LiftLower, WasmSignature, WasmType}, - name_package_module, uwrite, uwriteln, + name_package_module, + symbol_name::{make_external_component, make_external_symbol}, + uwrite, uwriteln, wit_parser::{ Alignment, ArchitectureSize, Docs, Function, FunctionKind, Handle, Int, InterfaceId, Param, Resolve, SizeAlign, Stability, Type, TypeDef, TypeDefKind, TypeId, TypeOwner, WorldId, @@ -24,7 +25,6 @@ use wit_bindgen_core::{ use wit_parser::TypeIdVisitor; // mod wamr; -mod symbol_name; pub const RESOURCE_IMPORT_BASE_CLASS_NAME: &str = "ResourceImportBase"; pub const RESOURCE_EXPORT_BASE_CLASS_NAME: &str = "ResourceExportBase"; diff --git a/crates/guest-rust/src/lib.rs b/crates/guest-rust/src/lib.rs index 3efde07ad..8d4ec6a2d 100644 --- a/crates/guest-rust/src/lib.rs +++ b/crates/guest-rust/src/lib.rs @@ -894,6 +894,39 @@ extern crate std; /// }); /// ``` /// +/// ## Native (non-WebAssembly) targets +/// +/// Generated bindings also compile for native targets, which is useful for +/// testing component code without a wasm runtime or for building it as a +/// `cdylib` plugin. Native linkers don't accept the `:`, `/`, `#`, `[` and +/// `]` characters that canonical ABI symbol names use, so on native targets +/// symbols are hex-encoded with the scheme in +/// `wit_bindgen_core::symbol_name` (the same one the C++ generator uses). +/// +/// Imports are not resolved by the native linker. Each import calls through +/// a function pointer that starts out null, and a host provides an +/// implementation at load time by calling the generated +/// `__wit_bindgen_register_` function with a function pointer +/// of the import's core signature (`` here is +/// `make_external_symbol(module, name, GuestImport)`). This means everything +/// links whether or not a host is present: a host only needs to register the +/// imports it actually implements, and calling an import that was never +/// registered aborts with a message naming the import and its registration +/// function. +/// +/// Exports, including post-return functions, async callbacks, and resource +/// destructors, are exported under their hex-encoded core export names. A +/// `__wit_bindgen_cabi_realloc_` function is also exported so hosts +/// can allocate guest-owned memory when lowering arguments, as the canonical +/// ABI requires. +/// +/// The `` prefix above is a hex-encoded +/// `/`, which keeps two `generate!` +/// invocations in one binary from defining the same symbols. Note that +/// binding the same world twice in one native binary will fail to link with +/// duplicate symbols unless `type_section_suffix` is used to tell the two +/// apart. +/// /// [WIT package]: https://component-model.bytecodealliance.org/design/packages.html #[cfg(feature = "macros")] pub use wit_bindgen_rust_macro::generate; diff --git a/crates/guest-rust/src/rt/mod.rs b/crates/guest-rust/src/rt/mod.rs index 349f8006a..889f5a5a5 100644 --- a/crates/guest-rust/src/rt/mod.rs +++ b/crates/guest-rust/src/rt/mod.rs @@ -153,7 +153,7 @@ pub fn maybe_link_cabi_realloc() { /// `cabi_realloc` module above. It's otherwise never explicitly called. /// /// For more information about this see `./ci/rebuild-libwit-bindgen-cabi.sh`. -#[cfg(any(target_env = "p1", target_env = ""))] +#[cfg(any(target_env = "p1", target_env = "", not(target_arch = "wasm32")))] pub unsafe fn cabi_realloc( old_ptr: *mut u8, old_len: usize, diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index 767beb6a9..57fbd8ab4 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -67,6 +67,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { &rust_name, params, results, + self.r#gen.r#gen.native_symbols(), )); rust_name } diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index fc809bad0..b82444b17 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -12,7 +12,7 @@ use std::fmt::Write as _; use std::mem; use wit_bindgen_core::abi::{self, AbiVariant, LiftLower}; use wit_bindgen_core::{ - AnonymousTypeGenerator, Source, TypeInfo, dealias, uwrite, uwriteln, wit_parser::*, + AnonymousTypeGenerator, Source, TypeInfo, dealias, symbol_name, uwrite, uwriteln, wit_parser::*, }; pub struct InterfaceGenerator<'a> { @@ -212,6 +212,7 @@ impl<'i> InterfaceGenerator<'i> { "new", &[abi::WasmType::Pointer], &[abi::WasmType::I32], + self.r#gen.native_symbols(), ); let import_rep = crate::declare_import( &wasm_import_module, @@ -219,6 +220,7 @@ impl<'i> InterfaceGenerator<'i> { "rep", &[abi::WasmType::I32], &[abi::WasmType::Pointer], + self.r#gen.native_symbols(), ); uwriteln!( self.src, @@ -347,7 +349,6 @@ macro_rules! {macro_name} {{ }; self.generate_raw_cabi_export(func, &ty, "$($path_to_types)*", async_); } - let export_prefix = self.r#gen.opts.export_prefix.as_deref().unwrap_or(""); for name in resources_to_drop { let module = match self.identifier { Identifier::Interface(_, key) => self.resolve.name_world_key(key), @@ -356,13 +357,13 @@ macro_rules! {macro_name} {{ } }; let camel = name.to_upper_camel_case(); + let attrs = self.core_export_attrs(&format!("{module}#[dtor]{name}")); uwriteln!( self.src, r#" const _: () = {{ #[doc(hidden)] - #[unsafe(export_name = "{export_prefix}{module}#[dtor]{name}")] - #[allow(non_snake_case)] + {attrs}#[allow(non_snake_case)] unsafe extern "C" fn dtor(rep: *mut u8) {{ unsafe {{ $($path_to_types)*::{camel}::dtor::< @@ -1019,6 +1020,7 @@ fn abi_layout(&mut self) -> ::core::alloc::Layout {{ "call", &sig.params, &sig.results, + self.r#gen.native_symbols(), ); let mut args = String::new(); for i in 0..params_lower.len() { @@ -1281,21 +1283,20 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8) Identifier::World(_) => None, 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}") } else { export_name.to_string() }; + + let attrs = self.core_export_attrs(&export_name); uwrite!( self.src, "\ - #[unsafe(export_name = \"{export_prefix}{export_name}\")] - unsafe extern \"C\" fn export_{name_snake}\ + {attrs}unsafe extern \"C\" fn export_{name_snake}\ ", ); - let params = self.print_export_sig(func, async_); self.push_str(" {\n"); uwriteln!( @@ -1305,13 +1306,12 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8) ); self.push_str("}\n"); - let export_prefix = self.r#gen.opts.export_prefix.as_deref().unwrap_or(""); if async_ { + let attrs = self.core_export_attrs(&format!("[callback]{export_name}")); uwrite!( self.src, "\ - #[unsafe(export_name = \"{export_prefix}[callback]{export_name}\")] - unsafe extern \"C\" fn _callback_{name_snake}(event0: u32, event1: u32, event2: u32) -> u32 {{ + {attrs}unsafe extern \"C\" fn _callback_{name_snake}(event0: u32, event1: u32, event2: u32) -> u32 {{ unsafe {{ {path_to_self}::__callback_{name_snake}(event0, event1, event2) }} @@ -1319,11 +1319,11 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8) " ); } else if abi::guest_export_needs_post_return(self.resolve, func) { + let attrs = self.core_export_attrs(&format!("cabi_post_{export_name}")); uwrite!( self.src, "\ - #[unsafe(export_name = \"{export_prefix}cabi_post_{export_name}\")] - unsafe extern \"C\" fn _post_return_{name_snake}\ + {attrs}unsafe extern \"C\" fn _post_return_{name_snake}\ " ); let params = self.print_post_return_sig(func); @@ -1337,6 +1337,24 @@ unsafe fn call_import(&mut self, _params: Self::ParamsLower, _results: *mut u8) } } + /// Returns the `export_name` attributes for a core export named + /// `export_name`. + /// + /// Has to exist due to the fact that native names cannot contain + /// special characters that wasm32 can like '/'. + /// + /// `cfg_attr` conditions are mutually exclusive, so exactly one attribute + /// applies on any target (for names that survive encoding unchanged, such + /// as `$root` exports, both carry the same string). + fn core_export_attrs(&self, export_name: &str) -> String { + let prefix = self.r#gen.opts.export_prefix.as_deref().unwrap_or(""); + let native = symbol_name::make_external_component(export_name); + format!( + "#[cfg_attr(target_arch = \"wasm32\", unsafe(export_name = \"{prefix}{export_name}\"))]\n\ + #[cfg_attr(not(target_arch = \"wasm32\"), unsafe(export_name = \"{prefix}{native}\"))]\n" + ) + } + fn print_export_sig(&mut self, func: &Function, async_: bool) -> Vec { self.src.push_str("("); let variant = if async_ { @@ -2952,6 +2970,7 @@ impl<'a> {camel}Borrow<'a>{{ "drop", &[abi::WasmType::I32], &[], + self.r#gen.native_symbols(), ); uwriteln!( self.src, diff --git a/crates/rust/src/lib.rs b/crates/rust/src/lib.rs index dfe8c2623..ec24a7331 100644 --- a/crates/rust/src/lib.rs +++ b/crates/rust/src/lib.rs @@ -10,8 +10,8 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use wit_bindgen_core::abi::{Bitcast, WasmType}; use wit_bindgen_core::{ - AsyncFilterSet, Files, InterfaceGenerator as _, Source, Types, WorldGenerator, dealias, - name_package_module, uwrite, uwriteln, wit_parser::*, + AsyncFilterSet, Files, InterfaceGenerator as _, Source, Types, WorldGenerator, abi, dealias, + name_package_module, symbol_name, uwrite, uwriteln, wit_parser::*, }; mod bindgen; @@ -47,6 +47,12 @@ pub struct RustWasm { used_member_attr_selectors: HashSet, world: Option, + /// Prefix applied to all native linkage symbols, set during `preprocess`. + /// This namespaces the symbols by world so that two `generate!` + /// invocations in the same crate don't collide, see + /// `RustWasm::native_symbols`. + native_symbols: Option, + rt_module: IndexSet, export_macros: Vec<(String, String)>, @@ -479,6 +485,12 @@ impl RustWasm { .unwrap_or("wit_bindgen::rt") } + fn native_symbols(&self) -> &str { + self.native_symbols + .as_deref() + .expect("native symbol prefix is set during preprocess") + } + fn map_type_path(&self) -> String { self.opts .map_type @@ -549,6 +561,28 @@ impl RustWasm { Ok(remapped) } + fn finish_native_cabi_realloc(&mut self) { + let prefix = self.native_symbols().to_string(); + let rt = self.runtime_path().to_string(); + let name = format!("__wit_bindgen_cabi_realloc_{prefix}"); + uwriteln!( + self.src, + r#" +#[cfg(not(target_arch = "wasm32"))] +#[unsafe(no_mangle)] +#[allow(non_snake_case)] +pub unsafe extern "C" fn {name}( + old_ptr: *mut u8, + old_len: usize, + align: usize, + new_len: usize, +) -> *mut u8 {{ + unsafe {{ {rt}::cabi_realloc(old_ptr, old_len, align, new_len) }} +}} +"# + ); + } + fn finish_runtime_module(&mut self) { if !self.rt_module.is_empty() { // As above, disable rustfmt, as we use prettyplease. @@ -1263,6 +1297,17 @@ impl WorldGenerator for RustWasm { }); self.world = Some(world); + self.native_symbols = Some({ + let w = &resolve.worlds[world]; + let pkg = w + .package + .map(|p| resolve.packages[p].name.to_string()) + .unwrap_or_default(); + let suffix = self.opts.type_section_suffix.as_deref().unwrap_or(""); + let name = format!("{pkg}/{}{suffix}", w.name); + format!("{}_", symbol_name::make_external_component(&name)) + }); + let world = &resolve.worlds[world]; // Specify that all imports local to the world's package should be // generated @@ -1491,6 +1536,8 @@ impl WorldGenerator for RustWasm { let exports = mem::take(&mut self.export_modules); self.emit_modules(exports); + self.finish_native_cabi_realloc(); + self.finish_runtime_module(); self.finish_export_macro(resolve, world); @@ -1864,6 +1911,7 @@ fn declare_import( rust_name: &str, params: &[WasmType], results: &[WasmType], + native_prefix: &str, ) -> String { let mut sig = "(".to_owned(); for param in params.iter() { @@ -1877,18 +1925,62 @@ fn declare_import( sig.push_str(" -> "); sig.push_str(wasm_type(*result)); } + + let symbol = symbol_name::make_external_symbol( + wasm_import_module, + wasm_import_name, + abi::AbiVariant::GuestImport, + ); + let ptr_static = format!("__WIT_BINDGEN_IMPORT_{native_prefix}{symbol}"); + let register_name = format!("__wit_bindgen_register_{native_prefix}{symbol}"); + let named_params: Vec = params + .iter() + .enumerate() + .map(|(i, ty)| format!("arg{i}: {}", wasm_type(*ty))) + .collect(); + let ret_sig = results + .first() + .map(|r| format!(" -> {}", wasm_type(*r))) + .unwrap_or_default(); + let call_args = (0..params.len()) + .map(|i| format!("arg{i}")) + .collect::>() + .join(", "); + let named_params_str = named_params.join(", "); + format!( - " - #[cfg(target_arch = \"wasm32\")] - #[link(wasm_import_module = \"{wasm_import_module}\")] - unsafe extern \"C\" {{ - #[link_name = \"{wasm_import_name}\"] + r#" + #[cfg(target_arch = "wasm32")] + #[link(wasm_import_module = "{wasm_import_module}")] + unsafe extern "C" {{ + #[link_name = "{wasm_import_name}"] fn {rust_name}{sig}; }} - #[cfg(not(target_arch = \"wasm32\"))] - unsafe extern \"C\" fn {rust_name}{sig} {{ unreachable!() }} - " + #[cfg(not(target_arch = "wasm32"))] + #[allow(non_upper_case_globals)] + static {ptr_static}: ::core::sync::atomic::AtomicPtr<()> = + ::core::sync::atomic::AtomicPtr::new(::core::ptr::null_mut()); + + #[cfg(not(target_arch = "wasm32"))] + #[unsafe(no_mangle)] + #[allow(non_snake_case)] + pub unsafe extern "C" fn {register_name}(func: unsafe extern "C" fn{sig}) {{ + {ptr_static}.store(func as *mut (), ::core::sync::atomic::Ordering::Release); + }} + + #[cfg(not(target_arch = "wasm32"))] + unsafe extern "C" fn {rust_name}({named_params_str}){ret_sig} {{ + let ptr = {ptr_static}.load(::core::sync::atomic::Ordering::Acquire); + assert!( + !ptr.is_null(), + "import `{wasm_import_module}#{wasm_import_name}` was called before the host \ + registered an implementation for it via `{register_name}`" + ); + let f: unsafe extern "C" fn{sig} = unsafe {{ ::core::mem::transmute(ptr) }}; + unsafe {{ f({call_args}) }} + }} + "#, ) } diff --git a/crates/rust/tests/codegen.rs b/crates/rust/tests/codegen.rs index e8046ceaf..79ca429fb 100644 --- a/crates/rust/tests/codegen.rs +++ b/crates/rust/tests/codegen.rs @@ -403,3 +403,109 @@ mod versioned_selectors { assert!(Alpha { x: 1 } < Alpha { x: 2 }); } } + +#[allow(unused, reason = "testing codegen, not functionality")] +mod native_symbols { + wit_bindgen::generate!({ + inline: r#" + package test:native; + + interface operations { + resource thing { + constructor(x: u32); + get: func() -> u32; + } + add: func(a: u32, b: u32) -> u32; + describe: func(value: u32) -> string; + } + + world test { + import operations; + export operations; + } + "#, + generate_all, + }); + + struct Component; + + impl exports::test::native::operations::Guest for Component { + type Thing = MyThing; + + fn add(a: u32, b: u32) -> u32 { + a + b + } + + fn describe(value: u32) -> String { + value.to_string() + } + } + + struct MyThing(u32); + + impl exports::test::native::operations::GuestThing for MyThing { + fn new(x: u32) -> Self { + MyThing(x) + } + + fn get(&self) -> u32 { + self.0 + } + } + + export!(Component); +} + +#[allow(unused, reason = "testing codegen, not functionality")] +mod native_symbols_async { + wit_bindgen::generate!({ + inline: r#" + package test:native-async; + + interface operations { + describe: func(value: u32) -> string; + } + + world test { + import operations; + export operations; + } + "#, + generate_all, + async: true, + }); + + struct Component; + + impl exports::test::native_async::operations::Guest for Component { + async fn describe(value: u32) -> String { + value.to_string() + } + } + + export!(Component); +} + +#[allow(unused, reason = "testing codegen, not functionality")] +mod native_symbols_shared_one { + wit_bindgen::generate!({ + inline: r#" + package test:native-shared; + interface operations { add: func(a: u32, b: u32) -> u32; } + world one { import operations; } + "#, + generate_all, + }); +} + +#[allow(unused, reason = "testing codegen, not functionality")] +mod native_symbols_shared_two { + wit_bindgen::generate!({ + inline: r#" + package test:native-shared; + interface operations { add: func(a: u32, b: u32) -> u32; } + world two { import operations; } + "#, + generate_all, + }); +}