diff --git a/.gitignore b/.gitignore index 3f7971eb8..0954a210e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ package-lock.json node_modules ace *.wasm +__pycache__ diff --git a/crates/gen-guest-rust/src/lib.rs b/crates/gen-guest-rust/src/lib.rs index 25dc8ba47..7a837617e 100644 --- a/crates/gen-guest-rust/src/lib.rs +++ b/crates/gen-guest-rust/src/lib.rs @@ -47,6 +47,21 @@ pub struct Opts { /// validation if it doesn't already have a `&str`. #[cfg_attr(feature = "clap", arg(long))] pub raw_strings: bool, + + /// The prefix to use when calling functions from within the generated + /// `export!` macro. + /// + /// This enables the generated `export!` macro to reference code from + /// another mod/crate. + #[cfg_attr(feature = "clap", arg(long))] + pub macro_call_prefix: Option, + + /// The name of the generated `export!` macro to use. + /// + /// If `None`, the name is derived from the name of the world in the + /// format `export_{world_name}!`. + #[cfg_attr(feature = "clap", arg(long))] + pub export_macro_name: Option, } impl Opts { @@ -111,7 +126,11 @@ impl WorldGenerator for RustWasm { fn finish(&mut self, name: &str, interfaces: &ComponentInterfaces, files: &mut Files) { if !self.exports.is_empty() { - let snake = name.to_snake_case(); + let macro_name = if let Some(name) = self.opts.export_macro_name.as_ref() { + name.to_snake_case() + } else { + format!("export_{}", name.to_snake_case()) + }; let macro_export = if self.opts.macro_export { "#[macro_export]" } else { @@ -123,9 +142,8 @@ impl WorldGenerator for RustWasm { /// Declares the export of the component's world for the /// given type. {macro_export} - macro_rules! export_{snake}(($t:ident) => {{ + macro_rules! {macro_name}(($t:ident) => {{ const _: () = {{ - " ); for src in self.exports.iter() { @@ -135,12 +153,23 @@ impl WorldGenerator for RustWasm { self.src, " }}; + + #[used] + #[doc(hidden)] + #[cfg(target_arch = \"wasm32\")] + static __FORCE_SECTION_REF: fn() = __force_section_ref; + #[doc(hidden)] + #[cfg(target_arch = \"wasm32\")] + fn __force_section_ref() {{ + {prefix}__link_section() + }} }}); - " + ", + prefix = self.opts.macro_call_prefix.as_deref().unwrap_or("") ); } - self.src.push_str("#[cfg(target_arch = \"wasm32\")]\n"); + self.src.push_str("\n#[cfg(target_arch = \"wasm32\")]\n"); // The custom section name here must start with "component-type" but // otherwise is attempted to be unique here to ensure that this doesn't get @@ -167,6 +196,15 @@ impl WorldGenerator for RustWasm { )); self.src.push_str(&format!("{:?};\n", component_type)); + self.src.push_str( + " + #[inline(never)] + #[doc(hidden)] + #[cfg(target_arch = \"wasm32\")] + pub fn __link_section() {} + ", + ); + let mut src = mem::take(&mut self.src); if self.opts.rustfmt { let mut child = Command::new("rustfmt") @@ -322,8 +360,9 @@ impl InterfaceGenerator<'_> { uwrite!( macro_src, " + #[doc(hidden)] #[export_name = \"{export_name}\"] - unsafe extern \"C\" fn export_{iface_snake}_{name_snake}(\ + unsafe extern \"C\" fn __export_{iface_snake}_{name_snake}(\ ", ); @@ -351,7 +390,11 @@ impl InterfaceGenerator<'_> { // Finish out the macro-generated export implementation. macro_src.push_str(" {\n"); - uwrite!(macro_src, "{module_name}::call_{name_snake}::<$t>("); + uwrite!( + macro_src, + "{prefix}{module_name}::call_{name_snake}::<$t>(", + prefix = self.gen.opts.macro_call_prefix.as_deref().unwrap_or("") + ); for param in params.iter() { uwrite!(macro_src, "{param},"); } @@ -386,8 +429,9 @@ impl InterfaceGenerator<'_> { uwrite!( macro_src, " + #[doc(hidden)] #[export_name = \"cabi_post_{export_name}\"] - pub unsafe extern \"C\" fn post_return_{iface_snake}_{name_snake}(\ + unsafe extern \"C\" fn __post_return_{iface_snake}_{name_snake}(\ " ); let mut params = Vec::new(); @@ -401,7 +445,11 @@ impl InterfaceGenerator<'_> { macro_src.push_str(") {\n"); // Finish out the macro here - uwrite!(macro_src, "{module_name}::post_return_{name_snake}::<$t>("); + uwrite!( + macro_src, + "{prefix}{module_name}::post_return_{name_snake}::<$t>(", + prefix = self.gen.opts.macro_call_prefix.as_deref().unwrap_or("") + ); for param in params.iter() { uwrite!(macro_src, "{param},"); } diff --git a/crates/gen-guest-rust/tests/codegen.rs b/crates/gen-guest-rust/tests/codegen.rs index 6477fd759..297a1259b 100644 --- a/crates/gen-guest-rust/tests/codegen.rs +++ b/crates/gen-guest-rust/tests/codegen.rs @@ -120,3 +120,57 @@ mod raw_strings { let _t: Vec = cat::bar(); } } + +// This is a static compilation test to ensure that +// export bindings can go inside of another mod/crate +// and still compile. +mod prefix { + mod bindings { + wit_bindgen_guest_rust::generate!({ + export_str["exports1"]: " + foo: func(x: string) + bar: func() -> string + ", + name: "baz", + macro_call_prefix: "bindings::" + }); + + pub(crate) use export_baz; + } + + struct Component; + + impl bindings::exports1::Exports1 for Component { + fn foo(x: String) { + println!("foo: {}", x); + } + + fn bar() -> String { + "bar".to_string() + } + } + + bindings::export_baz!(Component); +} + +// This is a static compilation test to check that +// the export macro name can be overridden. +mod macro_name { + wit_bindgen_guest_rust::generate!({ + export_str["exports2"]: " + foo: func(x: string) + ", + name: "baz", + export_macro_name: "jam" + }); + + struct Component; + + impl exports2::Exports2 for Component { + fn foo(x: String) { + println!("foo: {}", x); + } + } + + jam!(Component); +} diff --git a/crates/guest-rust-macro/src/lib.rs b/crates/guest-rust-macro/src/lib.rs index 609f02e09..8f01e45c5 100644 --- a/crates/guest-rust-macro/src/lib.rs +++ b/crates/guest-rust-macro/src/lib.rs @@ -1,5 +1,8 @@ use proc_macro::TokenStream; -use syn::parse::{Parse, ParseStream, Result}; +use syn::{ + parse::{Parse, ParseStream, Result}, + LitStr, Token, +}; use wit_bindgen_gen_guest_rust::Opts; #[proc_macro] @@ -11,12 +14,16 @@ mod kw { syn::custom_keyword!(unchecked); syn::custom_keyword!(no_std); syn::custom_keyword!(raw_strings); + syn::custom_keyword!(macro_call_prefix); + syn::custom_keyword!(export_macro_name); } enum Opt { Unchecked, NoStd, RawStrings, + MacroCallPrefix(LitStr), + ExportMacroName(LitStr), } impl Parse for Opt { @@ -31,6 +38,14 @@ impl Parse for Opt { } else if l.peek(kw::raw_strings) { input.parse::()?; Ok(Opt::RawStrings) + } else if l.peek(kw::macro_call_prefix) { + input.parse::()?; + input.parse::()?; + Ok(Opt::MacroCallPrefix(input.parse()?)) + } else if l.peek(kw::export_macro_name) { + input.parse::()?; + input.parse::()?; + Ok(Opt::ExportMacroName(input.parse()?)) } else { Err(l.error()) } @@ -43,6 +58,8 @@ impl wit_bindgen_rust_macro_shared::Configure for Opt { Opt::Unchecked => opts.unchecked = true, Opt::NoStd => opts.no_std = true, Opt::RawStrings => opts.raw_strings = true, + Opt::MacroCallPrefix(prefix) => opts.macro_call_prefix = Some(prefix.value()), + Opt::ExportMacroName(name) => opts.export_macro_name = Some(name.value()), } } }