Skip to content

Commit 99dd079

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 f20a92e commit 99dd079

44 files changed

Lines changed: 3464 additions & 301 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
@@ -91,7 +91,14 @@ pub fn inject(
9191
impl<'a> CollectProcMacros<'a> {
9292
fn check_not_pub_in_root(&self, vis: &ast::Visibility, sp: Span) {
9393
if self.is_proc_macro_crate && self.in_root && vis.kind.is_pub() {
94-
self.dcx.emit_err(diagnostics::ProcMacro { span: sp });
94+
// On wasm we end up generating other public exports (though the exact specifics are
95+
// internal). For now omit this check, we'll need to refine it before stabilizing wasm
96+
// proc macros.
97+
if !(self.session.opts.unstable_opts.wasm_proc_macros
98+
&& self.session.target.is_like_wasm)
99+
{
100+
self.dcx.emit_err(diagnostics::ProcMacro { span: sp });
101+
}
95102
}
96103
}
97104

@@ -277,6 +284,9 @@ impl<'a> Visitor<'a> for CollectProcMacros<'a> {
277284
// // ...
278285
// ];
279286
// }
287+
//
288+
// If we're targeting wasm32, we also inject a macro call to generate_export!(DECLS). This produces
289+
// the WASI component model ABI exports/imports to support the proc macro's execution.
280290
fn mk_decls(cx: &mut ExtCtxt<'_>, macros: &[ProcMacro]) -> Box<ast::Item> {
281291
let expn_id = cx.resolver.expansion_for_ast_pass(
282292
DUMMY_SP,
@@ -382,9 +392,33 @@ fn mk_decls(cx: &mut ExtCtxt<'_>, macros: &[ProcMacro]) -> Box<ast::Item> {
382392
cx.attr_nested_word(sym::allow, sym::deprecated, span),
383393
]);
384394

385-
let block = ast::ConstItemRhsKind::new_body(cx.expr_block(
386-
cx.block(span, thin_vec![cx.stmt_item(span, krate), cx.stmt_item(span, decls_static)]),
387-
));
395+
// For wasm targets, exporting from proc-macros requires that we generate additional symbols
396+
// (not just the single #[used] static). We use a macro defined in the proc_macro crate to do
397+
// so.
398+
let block_contents = if cx.sess.target.is_like_wasm {
399+
let mac_call = cx.stmt_semi(cx.expr_macro_call(
400+
span,
401+
cx.macro_call(
402+
span,
403+
cx.path(
404+
span,
405+
vec![proc_macro, bridge, client, Ident::new(sym::generate_export, span)],
406+
),
407+
rustc_ast::token::Delimiter::Parenthesis,
408+
rustc_ast::tokenstream::TokenStream::new(vec![
409+
rustc_ast::tokenstream::TokenTree::Token(
410+
rustc_ast::token::Token::from_ast_ident(Ident::new(sym::_DECLS, span)),
411+
rustc_ast::tokenstream::Spacing::Alone,
412+
),
413+
]),
414+
),
415+
));
416+
thin_vec![cx.stmt_item(span, krate), cx.stmt_item(span, decls_static), mac_call]
417+
} else {
418+
thin_vec![cx.stmt_item(span, krate), cx.stmt_item(span, decls_static)]
419+
};
420+
421+
let block = ast::ConstItemRhsKind::new_body(cx.expr_block(cx.block(span, block_contents)));
388422

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

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", "all-arch", "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)