Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ package-lock.json
node_modules
ace
*.wasm
__pycache__
66 changes: 57 additions & 9 deletions crates/gen-guest-rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,

/// 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<String>,
}

impl Opts {
Expand Down Expand Up @@ -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 {
Expand All @@ -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() {
Expand All @@ -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
Expand All @@ -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")
Expand Down Expand Up @@ -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}(\
",
);

Expand Down Expand Up @@ -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},");
}
Expand Down Expand Up @@ -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();
Expand All @@ -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},");
}
Expand Down
54 changes: 54 additions & 0 deletions crates/gen-guest-rust/tests/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,57 @@ mod raw_strings {
let _t: Vec<u8> = 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);
}
19 changes: 18 additions & 1 deletion crates/guest-rust-macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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]
Expand All @@ -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 {
Expand All @@ -31,6 +38,14 @@ impl Parse for Opt {
} else if l.peek(kw::raw_strings) {
input.parse::<kw::raw_strings>()?;
Ok(Opt::RawStrings)
} else if l.peek(kw::macro_call_prefix) {
input.parse::<kw::macro_call_prefix>()?;
input.parse::<Token![:]>()?;
Ok(Opt::MacroCallPrefix(input.parse()?))
} else if l.peek(kw::export_macro_name) {
input.parse::<kw::export_macro_name>()?;
input.parse::<Token![:]>()?;
Ok(Opt::ExportMacroName(input.parse()?))
} else {
Err(l.error())
}
Expand All @@ -43,6 +58,8 @@ impl wit_bindgen_rust_macro_shared::Configure<Opts> 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()),
}
}
}