Skip to content

Commit adc2c08

Browse files
Implement support for proc macros on wasm32-wasip2
* `cc` crate dependency causes us to need an old wasmtime dependency. This is getting relaxed upstream and should be fixed in wasmtime v46.
1 parent 9935c83 commit adc2c08

58 files changed

Lines changed: 3503 additions & 158 deletions

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: 1136 additions & 40 deletions
Large diffs are not rendered by default.

bootstrap.example.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,15 @@
915915
#
916916
#rust.parallel-frontend-threads = 1
917917

918+
# Enables building a wasm proc macro compatible toolchain.
919+
#
920+
# This requires building an additional standard library for a different target and adding it
921+
# to the sysroot before running tests, and so needs special handling in bootstrap. Currently
922+
# off by default.
923+
#
924+
# This currently opts compiletest into running/building proc-macro tests via wasm.
925+
#rust.wasm-proc-macro = false
926+
918927
# =============================================================================
919928
# Distribution options
920929
#

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.18"
3333
tracing = "0.1"
34+
wit-bindgen-core = "0.57.1"
35+
wit-bindgen-rust = "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: 103 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,68 @@ 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! ";
446+
let src = src.replace(
447+
needle,
448+
// #[allow_internal_unstable(...)] is not transitive on macro expansions and so
449+
// doesn't apply to this inner macro -- which also references unstable types from
450+
// the generated bindings.
451+
//
452+
// This string replacement is obviously a hack, but it works OK in practice. If you
453+
// can remove this and keep wasm proc macro tests passing, go for it.
454+
&format!("#[allow_internal_unstable(proc_macro_internals)] {needle}"),
455+
);
456+
assert!(src.contains(needle), "{}", src);
457+
let output = rustc_parse::source_str_to_stream(
458+
ecx.psess(),
459+
rustc_span::FileName::proc_macro_source_code(&src),
460+
src,
461+
Some(call_site),
462+
);
463+
464+
match output {
465+
Ok(o) => Ok(o),
466+
Err(diags) => {
467+
for diag in diags {
468+
diag.emit();
469+
}
470+
471+
Err(ecx
472+
.dcx()
473+
.span_delayed_bug(span, "failed to parse wit-bindgen generated source"))
474+
}
475+
}
476+
}
477+
}

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.18"
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", "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)