From 295781eb589c379a73e1d1088088c485445b6be3 Mon Sep 17 00:00:00 2001 From: Bjorn Beishline <75190918+BjornTheProgrammer@users.noreply.github.com> Date: Thu, 12 Mar 2026 16:38:37 -0700 Subject: [PATCH 1/2] feat(rust): basic macro stub --- crates/guest-rust/macro/src/lib.rs | 9 +++++++++ crates/rust/src/bindgen.rs | 1 + crates/rust/src/interface.rs | 4 ++++ crates/rust/src/lib.rs | 16 +++++++++++++++- 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/crates/guest-rust/macro/src/lib.rs b/crates/guest-rust/macro/src/lib.rs index 91d4335dd..e8cbc91b3 100644 --- a/crates/guest-rust/macro/src/lib.rs +++ b/crates/guest-rust/macro/src/lib.rs @@ -165,6 +165,9 @@ impl Parse for Config { } opts.async_ = val; } + Opt::NativeStubMacro(macro_str) => { + opts.native_macro_stub = Some(macro_str.to_token_stream().to_string()); + } } } } else { @@ -317,6 +320,7 @@ mod kw { syn::custom_keyword!(disable_custom_section_link_helpers); syn::custom_keyword!(imports); syn::custom_keyword!(debug); + syn::custom_keyword!(native_stub_macro); } #[derive(Clone)] @@ -373,6 +377,7 @@ enum Opt { DisableCustomSectionLinkHelpers(syn::LitBool), Async(AsyncFilterSet, Span), Debug(syn::LitBool), + NativeStubMacro(syn::Path), } impl Parse for Opt { @@ -546,6 +551,10 @@ impl Parse for Opt { } Ok(Opt::Async(set, span)) } + } else if l.peek(kw::native_stub_macro) { + input.parse::()?; + input.parse::()?; + Ok(Opt::NativeStubMacro(input.parse()?)) } else { Err(l.error()) } diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index cd76e0b75..83c28aad1 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -64,6 +64,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { &rust_name, params, results, + &self.r#gen.r#gen.opts.native_macro_stub, )); rust_name } diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index 4f4f00471..be0f76631 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -209,6 +209,7 @@ impl<'i> InterfaceGenerator<'i> { "new", &[abi::WasmType::Pointer], &[abi::WasmType::I32], + &self.r#gen.opts.native_macro_stub, ); let import_rep = crate::declare_import( &wasm_import_module, @@ -216,6 +217,7 @@ impl<'i> InterfaceGenerator<'i> { "rep", &[abi::WasmType::I32], &[abi::WasmType::Pointer], + &self.r#gen.opts.native_macro_stub, ); uwriteln!( self.src, @@ -956,6 +958,7 @@ fn abi_layout(&mut self) -> ::core::alloc::Layout {{ "call", &sig.params, &sig.results, + &self.r#gen.opts.native_macro_stub, ); let mut args = String::new(); for i in 0..params_lower.len() { @@ -2799,6 +2802,7 @@ impl<'a> {camel}Borrow<'a>{{ "drop", &[abi::WasmType::I32], &[], + &self.r#gen.opts.native_macro_stub, ); uwriteln!( self.src, diff --git a/crates/rust/src/lib.rs b/crates/rust/src/lib.rs index 3df749648..c3036f73e 100644 --- a/crates/rust/src/lib.rs +++ b/crates/rust/src/lib.rs @@ -287,6 +287,10 @@ pub struct Opts { arg(long, require_equals = true, value_name = "true|false") )] pub merge_structurally_equal_types: Option>, + + /// Enables macros to be stubbed by a user provided macro instead of the + /// default unwrap for native implementations. + pub native_macro_stub: Option, } impl Opts { @@ -1741,6 +1745,7 @@ fn declare_import( rust_name: &str, params: &[WasmType], results: &[WasmType], + native_stub_macro: &Option, ) -> String { let mut sig = "(".to_owned(); for param in params.iter() { @@ -1754,6 +1759,15 @@ fn declare_import( sig.push_str(" -> "); sig.push_str(wasm_type(*result)); } + + let native_stub = if let Some(macro_path) = native_stub_macro { + format!( + "unsafe extern \"C\" fn {rust_name}{sig} {{ {macro_path}!(\"{wasm_import_module}\", \"{wasm_import_name}\", fn {rust_name}{sig}) }}" + ) + } else { + format!("unsafe extern \"C\" fn {rust_name}{sig} {{ unreachable!() }}") + }; + format!( " #[cfg(target_arch = \"wasm32\")] @@ -1764,7 +1778,7 @@ fn declare_import( }} #[cfg(not(target_arch = \"wasm32\"))] - unsafe extern \"C\" fn {rust_name}{sig} {{ unreachable!() }} + {native_stub} " ) } From 37060655f362ecc5a21dcfffb27a4e032e73ab11 Mon Sep 17 00:00:00 2001 From: Bjorn Beishline <75190918+BjornTheProgrammer@users.noreply.github.com> Date: Thu, 12 Mar 2026 22:25:55 -0700 Subject: [PATCH 2/2] feat(rust): implement native stub macro --- crates/guest-rust/macro/src/lib.rs | 26 +++++++-- crates/rust/src/bindgen.rs | 3 +- crates/rust/src/interface.rs | 12 ++-- crates/rust/src/lib.rs | 93 +++++++++++++++++++++++++++--- 4 files changed, 115 insertions(+), 19 deletions(-) diff --git a/crates/guest-rust/macro/src/lib.rs b/crates/guest-rust/macro/src/lib.rs index e8cbc91b3..97baf436a 100644 --- a/crates/guest-rust/macro/src/lib.rs +++ b/crates/guest-rust/macro/src/lib.rs @@ -10,7 +10,7 @@ use syn::{LitStr, Token, braced, token}; use wit_bindgen_core::AsyncFilterSet; use wit_bindgen_core::WorldGenerator; use wit_bindgen_core::wit_parser::{PackageId, Resolve, UnresolvedPackageGroup, WorldId}; -use wit_bindgen_rust::{Opts, Ownership, WithOption}; +use wit_bindgen_rust::{NativeStubMacro, Opts, Ownership, WithOption}; #[proc_macro] pub fn generate(input: proc_macro::TokenStream) -> proc_macro::TokenStream { @@ -49,6 +49,7 @@ struct Config { world: WorldId, files: Vec, debug: bool, + native_stub_macro: Option, } /// The source of the wit package definition @@ -68,6 +69,7 @@ impl Parse for Config { let mut features = Vec::new(); let mut async_configured = false; let mut debug = false; + let mut native_stub_macro = None; if input.peek(token::Brace) { let content; @@ -165,8 +167,11 @@ impl Parse for Config { } opts.async_ = val; } - Opt::NativeStubMacro(macro_str) => { - opts.native_macro_stub = Some(macro_str.to_token_stream().to_string()); + Opt::NativeStubMacro(path, extra) => { + native_stub_macro = Some(NativeStubMacro { + macro_path: path.to_token_stream().to_string(), + extra_args: extra.map(|t| t.to_string()), + }); } } } @@ -189,6 +194,7 @@ impl Parse for Config { world, files, debug, + native_stub_macro, }) } } @@ -248,6 +254,7 @@ impl Config { fn expand(mut self) -> Result { let mut files = Default::default(); let mut generator = self.opts.build(); + generator.native_stub_macro = self.native_stub_macro; generator .generate(&mut self.resolve, self.world, &mut files) .map_err(|e| anyhow_to_syn(Span::call_site(), e))?; @@ -377,7 +384,7 @@ enum Opt { DisableCustomSectionLinkHelpers(syn::LitBool), Async(AsyncFilterSet, Span), Debug(syn::LitBool), - NativeStubMacro(syn::Path), + NativeStubMacro(syn::Path, Option), } impl Parse for Opt { @@ -554,7 +561,16 @@ impl Parse for Opt { } else if l.peek(kw::native_stub_macro) { input.parse::()?; input.parse::()?; - Ok(Opt::NativeStubMacro(input.parse()?)) + let macro_path: syn::Path = input.parse()?; + let extra = if input.peek(Token![!]) { + input.parse::()?; + let content; + syn::parenthesized!(content in input); + Some(content.parse::()?) + } else { + None + }; + Ok(Opt::NativeStubMacro(macro_path, extra)) } else { Err(l.error()) } diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index 83c28aad1..19e46262f 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -64,7 +64,8 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { &rust_name, params, results, - &self.r#gen.r#gen.opts.native_macro_stub, + &self.r#gen.r#gen.native_stub_macro, + &mut self.r#gen.r#gen.native_stub_methods, )); rust_name } diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index be0f76631..69bf22669 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -209,7 +209,8 @@ impl<'i> InterfaceGenerator<'i> { "new", &[abi::WasmType::Pointer], &[abi::WasmType::I32], - &self.r#gen.opts.native_macro_stub, + &self.r#gen.native_stub_macro, + &mut self.r#gen.native_stub_methods, ); let import_rep = crate::declare_import( &wasm_import_module, @@ -217,7 +218,8 @@ impl<'i> InterfaceGenerator<'i> { "rep", &[abi::WasmType::I32], &[abi::WasmType::Pointer], - &self.r#gen.opts.native_macro_stub, + &self.r#gen.native_stub_macro, + &mut self.r#gen.native_stub_methods, ); uwriteln!( self.src, @@ -958,7 +960,8 @@ fn abi_layout(&mut self) -> ::core::alloc::Layout {{ "call", &sig.params, &sig.results, - &self.r#gen.opts.native_macro_stub, + &self.r#gen.native_stub_macro, + &mut self.r#gen.native_stub_methods, ); let mut args = String::new(); for i in 0..params_lower.len() { @@ -2802,7 +2805,8 @@ impl<'a> {camel}Borrow<'a>{{ "drop", &[abi::WasmType::I32], &[], - &self.r#gen.opts.native_macro_stub, + &self.r#gen.native_stub_macro, + &mut self.r#gen.native_stub_methods, ); uwriteln!( self.src, diff --git a/crates/rust/src/lib.rs b/crates/rust/src/lib.rs index c3036f73e..11ec728e5 100644 --- a/crates/rust/src/lib.rs +++ b/crates/rust/src/lib.rs @@ -52,6 +52,23 @@ pub struct RustWasm { future_payloads: IndexMap, String>, stream_payloads: IndexMap, String>, + + pub native_stub_macro: Option, + pub native_stub_methods: Vec, +} + +pub struct NativeStubMacro { + pub macro_path: String, + pub extra_args: Option, +} + +#[derive(Debug, Clone)] +pub struct NativeStubMethod { + pub wasm_import_module: String, + pub wasm_import_name: String, + pub rust_name: String, + pub params: Vec, + pub results: Vec, } #[derive(Default)] @@ -287,10 +304,6 @@ pub struct Opts { arg(long, require_equals = true, value_name = "true|false") )] pub merge_structurally_equal_types: Option>, - - /// Enables macros to be stubbed by a user provided macro instead of the - /// default unwrap for native implementations. - pub native_macro_stub: Option, } impl Opts { @@ -828,6 +841,50 @@ impl As{upcase} for {to_convert} {{ } } + fn finish_native_stubs(&mut self) { + let Some(stub) = &self.native_stub_macro else { + return; + }; + if self.native_stub_methods.is_empty() { + return; + } + + let macro_path = &stub.macro_path; + let extra = match &stub.extra_args { + Some(args) => format!("{args}, "), + None => String::new(), + }; + + let mut entries = String::new(); + for method in self.native_stub_methods.iter() { + let NativeStubMethod { + wasm_import_module, + wasm_import_name, + rust_name, + params, + results, + } = &method; + + entries.push_str(&format!( + " \"{wasm_import_module}\", \"{wasm_import_name}\", fn {rust_name}(" + )); + + for (i, param) in params.iter().enumerate() { + if i > 0 { + entries.push_str(", "); + } + entries.push_str(&format!("p{i}: {}", wasm_type(*param))); + } + entries.push(')'); + if !method.results.is_empty() { + entries.push_str(&format!(" -> {}", wasm_type(results[0]))); + } + entries.push_str(";\n"); + } + + uwriteln!(self.src, "{macro_path}!(@definitions {extra}{entries});"); + } + /// Generates an `export!` macro for the `world_id` specified. /// /// This will generate a macro which will then itself invoke all the @@ -1396,6 +1453,7 @@ impl WorldGenerator for RustWasm { self.emit_modules(exports); self.finish_runtime_module(); + self.finish_native_stubs(); self.finish_export_macro(resolve, world); // This is a bit tricky, but we sometimes want to "split" the `world` in @@ -1745,11 +1803,12 @@ fn declare_import( rust_name: &str, params: &[WasmType], results: &[WasmType], - native_stub_macro: &Option, + native_stub_macro: &Option, + native_stub_methods: &mut Vec, ) -> String { let mut sig = "(".to_owned(); - for param in params.iter() { - sig.push_str("_: "); + for (i, param) in params.iter().enumerate() { + sig.push_str(&format!("p{i}: ")); sig.push_str(wasm_type(*param)); sig.push_str(", "); } @@ -1760,9 +1819,25 @@ fn declare_import( sig.push_str(wasm_type(*result)); } - let native_stub = if let Some(macro_path) = native_stub_macro { + let native_stub = if let Some(stub) = native_stub_macro { + native_stub_methods.push(NativeStubMethod { + wasm_import_module: wasm_import_module.to_string(), + wasm_import_name: wasm_import_name.to_string(), + rust_name: rust_name.to_string(), + params: params.to_vec(), + results: results.to_vec(), + }); + + let macro_path = &stub.macro_path; + let extra = match &stub.extra_args { + Some(args) => format!("{args}, "), + None => String::new(), + }; format!( - "unsafe extern \"C\" fn {rust_name}{sig} {{ {macro_path}!(\"{wasm_import_module}\", \"{wasm_import_name}\", fn {rust_name}{sig}) }}" + "unsafe extern \"C\" fn {rust_name}{sig} {{ \ + {macro_path}!({extra} \"{wasm_import_module}\", \ + \"{wasm_import_name}\", fn {rust_name}{sig}) \ + }}" ) } else { format!("unsafe extern \"C\" fn {rust_name}{sig} {{ unreachable!() }}")