Skip to content

Commit 432f75c

Browse files
Implement support for proc macros on wasm32-wasip2
This is an initial draft implementation. A few problems need to be addressed before this could be merged: * Figure out testing strategy -- currently, this hardcodes compiletest to build in wasip2, which clearly won't work to land. Most likely we'll need an opt-in of some kind, especially given that building the standard library for wasip2 currently requires downloading a wasi sysroot. * This duplicates the proc macro ABI with a WIT file for all the individual functions. There's not too much complexity inherent to the WIT approach -- the duplication doesn't seem to cost us much, since the ABI is pretty simple. But it does mean the base data structures need replicating, though some of that could be eliminated by always using the codegen'd struct/enums in the C bridge as well. * There's also some impedance mismatch, e.g., Span is Copy and so we end up leaking resources to mitigate the issue there. I think the right thing is probably to expose it as a u32 wrapper (similar to the old ABI). But in the meantime this does pass tests (confirmed on aarch64-apple and x86_64-linux, the two systems I have readily available), so opening up a draft PR to share the initial state. Some not necessarily blocking challenges: * `cc` crate dependency causes us to need an old wasmtime dependency. I think we should be able to relax the upstream constraint but haven't looked into it yet.
1 parent 485ec3f commit 432f75c

43 files changed

Lines changed: 3345 additions & 295 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 1190 additions & 46 deletions
Large diffs are not rendered by default.

compiler/rustc_builtin_macros/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ rustc_target = { path = "../rustc_target" }
3131
smallvec = { version = "1.8.1", features = ["union", "may_dangle"] }
3232
thin-vec = "0.2.15"
3333
tracing = "0.1"
34+
wit-bindgen-rust = "0.57.1"
35+
wit-bindgen-core = "0.57.1"
3436
# tidy-alphabetical-end
3537

3638
[features]

compiler/rustc_builtin_macros/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,9 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) {
145145
register(sym::contracts_requires, requires);
146146
let ensures = SyntaxExtensionKind::Attr(Arc::new(contracts::ExpandEnsures));
147147
register(sym::contracts_ensures, ensures);
148+
149+
register(
150+
sym::internal_wit_bindgen,
151+
SyntaxExtensionKind::Bang(Arc::new(proc_macro_harness::InternalWitBindgen)),
152+
);
148153
}

compiler/rustc_builtin_macros/src/proc_macro_harness.rs

Lines changed: 101 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,14 @@ pub fn inject(
8989
impl<'a> CollectProcMacros<'a> {
9090
fn check_not_pub_in_root(&self, vis: &ast::Visibility, sp: Span) {
9191
if self.is_proc_macro_crate && self.in_root && vis.kind.is_pub() {
92-
self.dcx.emit_err(diagnostics::ProcMacro { span: sp });
92+
// On wasm we end up generating other public exports (though the exact specifics are
93+
// internal). For now omit this check, we'll need to refine it before stabilizing wasm
94+
// proc macros.
95+
if !(self.session.opts.unstable_opts.wasm_proc_macros
96+
&& self.session.target.is_like_wasm)
97+
{
98+
self.dcx.emit_err(diagnostics::ProcMacro { span: sp });
99+
}
93100
}
94101
}
95102

@@ -270,6 +277,9 @@ impl<'a> Visitor<'a> for CollectProcMacros<'a> {
270277
// // ...
271278
// ];
272279
// }
280+
//
281+
// If we're targeting wasm32, we also inject a macro call to generate_export!(DECLS). This produces
282+
// the WASI component model ABI exports/imports to support the proc macro's execution.
273283
fn mk_decls(cx: &mut ExtCtxt<'_>, macros: &[ProcMacro]) -> Box<ast::Item> {
274284
let expn_id = cx.resolver.expansion_for_ast_pass(
275285
DUMMY_SP,
@@ -361,9 +371,33 @@ fn mk_decls(cx: &mut ExtCtxt<'_>, macros: &[ProcMacro]) -> Box<ast::Item> {
361371
cx.attr_nested_word(sym::allow, sym::deprecated, span),
362372
]);
363373

364-
let block = ast::ConstItemRhsKind::new_body(cx.expr_block(
365-
cx.block(span, thin_vec![cx.stmt_item(span, krate), cx.stmt_item(span, decls_static)]),
366-
));
374+
// For wasm targets, exporting from proc-macros requires that we generate additional symbols
375+
// (not just the single #[used] static). We use a macro defined in the proc_macro crate to do
376+
// so.
377+
let block_contents = if cx.sess.target.is_like_wasm {
378+
let mac_call = cx.stmt_semi(cx.expr_macro_call(
379+
span,
380+
cx.macro_call(
381+
span,
382+
cx.path(
383+
span,
384+
vec![proc_macro, bridge, client, Ident::new(sym::generate_export, span)],
385+
),
386+
rustc_ast::token::Delimiter::Parenthesis,
387+
rustc_ast::tokenstream::TokenStream::new(vec![
388+
rustc_ast::tokenstream::TokenTree::Token(
389+
rustc_ast::token::Token::from_ast_ident(Ident::new(sym::_DECLS, span)),
390+
rustc_ast::tokenstream::Spacing::Alone,
391+
),
392+
]),
393+
),
394+
));
395+
thin_vec![cx.stmt_item(span, krate), cx.stmt_item(span, decls_static), mac_call]
396+
} else {
397+
thin_vec![cx.stmt_item(span, krate), cx.stmt_item(span, decls_static)]
398+
};
399+
400+
let block = ast::ConstItemRhsKind::new_body(cx.expr_block(cx.block(span, block_contents)));
367401

368402
let anon_constant = cx.item_const(
369403
span,
@@ -376,3 +410,66 @@ fn mk_decls(cx: &mut ExtCtxt<'_>, macros: &[ProcMacro]) -> Box<ast::Item> {
376410
let items = AstFragment::Items(smallvec![anon_constant]);
377411
cx.monotonic_expander().fully_expand_fragment(items).make_items().pop().unwrap()
378412
}
413+
414+
pub(crate) struct InternalWitBindgen;
415+
416+
impl rustc_expand::base::BangProcMacro for InternalWitBindgen {
417+
fn expand<'cx>(
418+
&self,
419+
ecx: &'cx mut ExtCtxt<'_>,
420+
span: Span,
421+
_ts: rustc_ast::tokenstream::TokenStream,
422+
) -> Result<rustc_ast::tokenstream::TokenStream, rustc_span::ErrorGuaranteed> {
423+
let mut options = wit_bindgen_rust::Opts::default();
424+
options.pub_export_macro = true;
425+
426+
let mut resolve = wit_bindgen_core::wit_parser::Resolve::default();
427+
let pkg_id = resolve
428+
.push_str(
429+
"proc-macro-wasm.wit",
430+
include_str!("../../../library/proc_macro/wasm-interface.wit"),
431+
)
432+
.unwrap();
433+
let world = resolve.select_world(&[pkg_id], None).unwrap();
434+
435+
let mut generator = options.build();
436+
let mut files = Default::default();
437+
wit_bindgen_core::WorldGenerator::generate(&mut generator, &mut resolve, world, &mut files)
438+
.expect("generation successful");
439+
let (_, src) = files.iter().next().unwrap();
440+
let src = std::str::from_utf8(src).unwrap();
441+
442+
let expn_data = ecx.current_expansion.id.expn_data();
443+
let call_site = ecx.with_call_site_ctxt(expn_data.call_site);
444+
445+
let needle = "macro_rules! __export_rust_lang_rust_custom_derive_cabi";
446+
let output = rustc_parse::source_str_to_stream(
447+
ecx.psess(),
448+
rustc_span::FileName::proc_macro_source_code(&src),
449+
src.replace(
450+
needle,
451+
// #[allow_internal_unstable(...)] is not transitive on macro expansions and so
452+
// doesn't apply to this inner macro -- which also references unstable types from
453+
// the generated bindings.
454+
//
455+
// This string replacement is obviously a hack, but it works OK in practice. If you
456+
// can remove this and keep wasm proc macro tests passing, go for it.
457+
&format!("#[allow_internal_unstable(proc_macro_internals)] {needle}"),
458+
),
459+
Some(call_site),
460+
);
461+
462+
match output {
463+
Ok(o) => Ok(o),
464+
Err(diags) => {
465+
for diag in diags {
466+
diag.emit();
467+
}
468+
469+
Err(ecx
470+
.dcx()
471+
.span_delayed_bug(span, "failed to parse wit-bindgen generated source"))
472+
}
473+
}
474+
}
475+
}

compiler/rustc_codegen_ssa/src/back/linker.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1811,13 +1811,25 @@ pub(crate) fn exported_symbols(
18111811
.collect();
18121812
}
18131813

1814-
let mut symbols = if let CrateType::ProcMacro = crate_type {
1815-
exported_symbols_for_proc_macro_crate(tcx)
1816-
} else {
1817-
exported_symbols_for_non_proc_macro(tcx, crate_type)
1818-
};
1814+
let mut symbols = Vec::new();
1815+
1816+
// We include all symbols in the export list if this is a regular crate, or if the
1817+
// -Zwasm-proc-macros flag is passed. With wasm proc macros we need to expose more than just the
1818+
// global static (in fact, that static is not used on wasm targets at all), and for now that
1819+
// just means letting it use 'normal' crate rules.
1820+
if crate_type != CrateType::ProcMacro || tcx.sess.opts.unstable_opts.wasm_proc_macros {
1821+
symbols.extend(exported_symbols_for_non_proc_macro(tcx, crate_type));
1822+
}
1823+
1824+
// If this is a proc macro, then add the proc macro specific symbols too.
1825+
// See comment above for more details.
1826+
if let CrateType::ProcMacro = crate_type {
1827+
symbols.extend(exported_symbols_for_proc_macro_crate(tcx));
1828+
}
18191829

1820-
if crate_type == CrateType::Dylib || crate_type == CrateType::ProcMacro {
1830+
if crate_type == CrateType::Dylib
1831+
|| (crate_type == CrateType::ProcMacro && !tcx.sess.target.is_like_wasm)
1832+
{
18211833
let metadata_symbol_name = exported_symbols::metadata_symbol_name(tcx);
18221834
symbols.push((metadata_symbol_name, SymbolExportKind::Data));
18231835
}

compiler/rustc_expand/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,10 @@ scoped-tls = "1.0"
3232
smallvec = { version = "1.8.1", features = ["union", "may_dangle"] }
3333
thin-vec = "0.2.15"
3434
tracing = "0.1"
35+
wasmtime-wasi = "38"
3536
# tidy-alphabetical-end
37+
38+
[dependencies.wasmtime]
39+
version = "38"
40+
default-features = false
41+
features = ["runtime", "winch", "component-model", "std", "cranelift"]

compiler/rustc_expand/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub mod config;
2222
pub mod expand;
2323
pub mod module;
2424
pub mod proc_macro;
25+
pub mod wasm_proc_macro;
2526

2627
pub fn provide(providers: &mut rustc_middle::query::Providers) {
2728
providers.derive_macro_expansion = proc_macro::provide_derive_macro_expansion;

0 commit comments

Comments
 (0)