From 6408d389fc3b43e4710a318b991c93014bd6cc8d Mon Sep 17 00:00:00 2001 From: Mark Rousskov Date: Mon, 10 Aug 2026 08:20:14 -0400 Subject: [PATCH] Add -Zwasm-proc-macro flag This is currently not really implemented, but partial support for it is added in various places across the tree. This is a patch extracted from the wider set of work to make review easier, which wires up: * Unstable flag in rustc * Bootstrap flag to enable using wasm-proc-macros (this needs an extra std compilation, wiring into compiletest) * Partial compiletest support for the flag (mostly just CLI side of things) --- bootstrap.example.toml | 12 +++++++++ compiler/rustc_interface/src/diagnostics.rs | 4 +++ compiler/rustc_interface/src/passes.rs | 6 +++++ compiler/rustc_interface/src/tests.rs | 1 + compiler/rustc_session/src/options.rs | 2 ++ src/bootstrap/src/core/build_steps/test.rs | 12 +++++++++ src/bootstrap/src/core/config/config.rs | 4 +++ src/bootstrap/src/core/config/toml/rust.rs | 2 ++ src/bootstrap/src/utils/cc_detect.rs | 11 ++++++-- .../src/compiler-flags/wasm-proc-macros.md | 8 ++++++ src/tools/compiletest/src/cli.rs | 6 +++++ src/tools/compiletest/src/common.rs | 2 ++ src/tools/compiletest/src/directives/cfg.rs | 6 +++++ .../src/directives/directive_names.rs | 2 ++ src/tools/compiletest/src/runtest.rs | 27 ++++++++++++++++++- src/tools/compiletest/src/rustdoc_gui_test.rs | 1 + 16 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 src/doc/unstable-book/src/compiler-flags/wasm-proc-macros.md diff --git a/bootstrap.example.toml b/bootstrap.example.toml index 3752f1bcc2ce6..418c5a4a9cb81 100644 --- a/bootstrap.example.toml +++ b/bootstrap.example.toml @@ -944,6 +944,18 @@ # because bootstrap will attempt to download the JSON docs data for this commit from its CI. #rust.stdlib-semver-baseline = "" +# Enables building a wasm proc macro compatible toolchain. +# +# This requires building an additional standard library for a different target and adding it +# to the sysroot before running tests, and so needs special handling in bootstrap. Currently +# off by default. +# +# This currently opts compiletest into running/building proc-macro tests via wasm. +# +# The implementation for this has not finished landing, so you probably don't +# want to enable this right now. +#rust.wasm-proc-macros = false + # ============================================================================= # Distribution options # diff --git a/compiler/rustc_interface/src/diagnostics.rs b/compiler/rustc_interface/src/diagnostics.rs index 44d6073b93037..9e6d4317c98e5 100644 --- a/compiler/rustc_interface/src/diagnostics.rs +++ b/compiler/rustc_interface/src/diagnostics.rs @@ -53,6 +53,10 @@ pub(crate) struct MixedBinCrate; #[diag("cannot mix `proc-macro` crate type with others")] pub(crate) struct MixedProcMacroCrate; +#[derive(Diagnostic)] +#[diag("cannot compile `proc-macro` crate to wasm targets without -Zwasm-proc-macros")] +pub(crate) struct UnstableWasmProcMacro; + #[derive(Diagnostic)] #[diag("error writing dependencies to `{$path}`: {$error}")] pub(crate) struct ErrorWritingDependencies<'a> { diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index cdba198297201..e54739043580f 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -275,6 +275,12 @@ fn configure_and_expand( sess.dcx().emit_err(diagnostics::MixedProcMacroCrate); } } + + if is_proc_macro_crate && sess.target.is_like_wasm && !sess.opts.unstable_opts.wasm_proc_macros + { + sess.dcx().emit_err(diagnostics::UnstableWasmProcMacro); + } + if crate_types.contains(&CrateType::Sdylib) && !tcx.features().export_stable() { feature_err(sess, sym::export_stable, DUMMY_SP, "`sdylib` crate type is unstable").emit(); } diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index f3a6dfea5959e..548ee3f4b8e7b 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -910,6 +910,7 @@ fn test_unstable_options_tracking_hash() { tracked!(verify_llvm_ir, true); tracked!(virtual_function_elimination, true); tracked!(wasi_exec_model, Some(WasiExecModel::Reactor)); + tracked!(wasm_proc_macros, true); // tidy-alphabetical-end macro_rules! tracked_no_crate_hash { diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index b3023c731eeff..20d1ff55eab6e 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -2979,6 +2979,8 @@ written to standard error output)"), // FIXME remove this after a couple releases wasm_c_abi: () = ((), parse_wasm_c_abi, [TRACKED], "use spec-compliant C ABI for `wasm32-unknown-unknown` (deprecated, always enabled)"), + wasm_proc_macros: bool = (false, parse_bool, [TRACKED], + "enable support for compiling and loading wasm proc macros"), write_long_types_to_disk: bool = (true, parse_bool, [UNTRACKED], "whether long type names should be written to files instead of being printed in errors"), // tidy-alphabetical-end diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index 6658087bae78c..dca3220acda55 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -2277,6 +2277,14 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the builder.ensure(compile::Rustc::new(test_compiler, target)); } + // Build the standard library for wasm32-wasip2 (current target for wasm proc macros). + if builder.config.wasm_proc_macros { + builder.ensure(compile::Std::new( + test_compiler, + TargetSelection::from_user("wasm32-wasip2"), + )); + } + if suite == "debuginfo" { builder.ensure(dist::DebuggerScripts { sysroot: builder.sysroot(test_compiler).to_path_buf(), @@ -2326,6 +2334,10 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the let is_rustdoc = suite == "rustdoc-ui" || suite == "rustdoc-js"; + if builder.config.wasm_proc_macros { + cmd.arg("--wasm-proc-macros"); + } + // There are (potentially) 2 `cargo`s to consider: // // - A "bootstrap" cargo, which is the same cargo used to build bootstrap itself, and is diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 4bc9de932f98f..fa10de3c92fac 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -343,6 +343,8 @@ pub struct Config { pub skip_std_check_if_no_download_rustc: bool, pub exec_ctx: ExecutionContext, + + pub wasm_proc_macros: bool, } impl Config { @@ -615,6 +617,7 @@ impl Config { break_on_ice: rust_break_on_ice, rustflags: rust_rustflags, stdlib_semver_baseline: rust_stdlib_semver_baseline, + wasm_proc_macros, } = toml_rust.unwrap_or_default(); let Llvm { @@ -1611,6 +1614,7 @@ NOTE: Please add `--stage 2` to your command line, or if you're sure you want to .unwrap_or(rust_debug == Some(true)), vendor, verbose_tests, + wasm_proc_macros: wasm_proc_macros.unwrap_or(false), windows_rc: build_windows_rc.map(PathBuf::from), yarn: build_yarn.map(PathBuf::from), // tidy-alphabetical-end diff --git a/src/bootstrap/src/core/config/toml/rust.rs b/src/bootstrap/src/core/config/toml/rust.rs index 33516fcbc9e4c..d0954e3ad70a5 100644 --- a/src/bootstrap/src/core/config/toml/rust.rs +++ b/src/bootstrap/src/core/config/toml/rust.rs @@ -75,6 +75,7 @@ define_config! { break_on_ice: Option = "break-on-ice", parallel_frontend_threads: Option = "parallel-frontend-threads", stdlib_semver_baseline: Option = "stdlib-semver-baseline", + wasm_proc_macros: Option = "wasm-proc-macros", } } @@ -393,6 +394,7 @@ pub fn check_incompatible_options_for_ci_rustc( bootstrap_override_lld: _, rustflags: _, stdlib_semver_baseline: _, + wasm_proc_macros: _, } = ci_rust_config; // There are two kinds of checks for CI rustc incompatible options: diff --git a/src/bootstrap/src/utils/cc_detect.rs b/src/bootstrap/src/utils/cc_detect.rs index 3c05302950844..86fe5cf946071 100644 --- a/src/bootstrap/src/utils/cc_detect.rs +++ b/src/bootstrap/src/utils/cc_detect.rs @@ -68,7 +68,7 @@ fn new_cc_build(build: &Build, target: TargetSelection) -> cc::Build { /// by combining the primary build target, host targets, and any additional targets. For /// each target, it calls [`fill_target_compiler`] to configure the necessary compiler tools. pub fn fill_compilers(build: &mut Build) { - let targets: HashSet<_> = match build.config.cmd { + let mut targets: HashSet<_> = match build.config.cmd { // We don't need to check cross targets for these commands. crate::Subcommand::Clean { .. } | crate::Subcommand::Check { .. } @@ -90,7 +90,14 @@ pub fn fill_compilers(build: &mut Build) { } }; - for target in targets.into_iter() { + // When we intend to build wasm proc macros, we'll need to detect a toolchain for linking those + // as well. In the future it would be good to make this a no-op given that we shouldn't need to + // build any C/C++ code for wasm... + if build.config.wasm_proc_macros { + targets.insert(TargetSelection::from_user("wasm32-wasip2")); + } + + for target in targets { fill_target_compiler(build, target); } } diff --git a/src/doc/unstable-book/src/compiler-flags/wasm-proc-macros.md b/src/doc/unstable-book/src/compiler-flags/wasm-proc-macros.md new file mode 100644 index 0000000000000..1f47f4373968e --- /dev/null +++ b/src/doc/unstable-book/src/compiler-flags/wasm-proc-macros.md @@ -0,0 +1,8 @@ +# `wasm-proc-macros` + +This option controls whether to enable support for compiling and loading +`--crate-type=proc-macro` to/from WASM rather than the normal host dylib target. + +Currently we expect that proc macros are compiled to the `wasm32-wasip2` +target. The exact target will likely change in the future. When this flag is +passed, both regular dylib proc macros and wasm proc macros are supported. diff --git a/src/tools/compiletest/src/cli.rs b/src/tools/compiletest/src/cli.rs index 1786c68a1889c..3af7b4dfeaeac 100644 --- a/src/tools/compiletest/src/cli.rs +++ b/src/tools/compiletest/src/cli.rs @@ -281,6 +281,10 @@ struct Args { /// Ignore `//@ ignore-backends` directives. #[arg(long)] bypass_ignore_backends: bool, + /// Build proc-macros for wasm. Assumes environment is configured to support this; e.g., std is + /// already built appropriately. + #[arg(long)] + wasm_proc_macros: bool, // These values can be entered multiple times, for example: // --skip foo --skip bar @@ -503,6 +507,8 @@ pub(crate) fn parse_config(args: Vec) -> Config { gcc_supported_target_tuples, + wasm_proc_macros: args.wasm_proc_macros, + jobs: args.jobs, parallel_frontend_threads, diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs index de5c50d234661..c5a631ad94589 100644 --- a/src/tools/compiletest/src/common.rs +++ b/src/tools/compiletest/src/common.rs @@ -766,6 +766,8 @@ pub(crate) struct Config { pub(crate) parallel_frontend_threads: u32, /// Number of times to execute each test. pub(crate) iteration_count: u32, + + pub(crate) wasm_proc_macros: bool, } impl Config { diff --git a/src/tools/compiletest/src/directives/cfg.rs b/src/tools/compiletest/src/directives/cfg.rs index b9036462f3447..19e08de1d062a 100644 --- a/src/tools/compiletest/src/directives/cfg.rs +++ b/src/tools/compiletest/src/directives/cfg.rs @@ -235,6 +235,12 @@ pub(crate) fn prepare_conditions(config: &Config) -> PreparedConditions { ); } + builder.cond( + "wasm-proc-macros", + config.wasm_proc_macros, + "when wasm-proc-macros is enabled in bootstrap.toml", + ); + // Coverage tests run the same test file in multiple modes. // If a particular test should not be run in one of the modes, ignore it // with "ignore-coverage-map" or "ignore-coverage-run". diff --git a/src/tools/compiletest/src/directives/directive_names.rs b/src/tools/compiletest/src/directives/directive_names.rs index b73e782252148..2a6783cbfbf1e 100644 --- a/src/tools/compiletest/src/directives/directive_names.rs +++ b/src/tools/compiletest/src/directives/directive_names.rs @@ -136,6 +136,7 @@ pub(crate) const KNOWN_DIRECTIVE_NAMES: &[&str] = &[ "ignore-vxworks", "ignore-wasi", "ignore-wasm", + "ignore-wasm-proc-macros", "ignore-wasm32", "ignore-wasm32-unknown-unknown", "ignore-wasm64", @@ -266,6 +267,7 @@ pub(crate) const KNOWN_DIRECTIVE_NAMES: &[&str] = &[ "only-uefi", "only-unix", "only-visionos", + "only-wasm-proc-macros", "only-wasm32", "only-wasm32-unknown-emscripten", "only-wasm32-unknown-unknown", diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 73e3b87b37aa8..8f45e037d0fc9 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -1351,7 +1351,27 @@ impl<'test> TestCx<'test> { let mut aux_props = self.props.from_aux_file(&aux_path, self.variant.revision(), self.config); if aux_type == Some(AuxType::ProcMacro) { - aux_props.force_host = true; + if self.config.wasm_proc_macros { + aux_props.compile_flags.push("--target=wasm32-wasip2".to_owned()); + // Override any earlier linkers for now, otherwise we fail to build since compiletest + // thinks we're building for a different target and passes its linker (if one is + // configured). + // + // wasm32-wasip2 should in principle always be able to link with wasm-component-ld + + // wasm-ld. This does mean that rust.lld needs to be enabled to build wasm-ld wrapper + // around rust-lld. + aux_props.compile_flags.push("-Clinker=wasm-component-ld".to_owned()); + aux_props.compile_flags.push(format!( + "-Clink-arg=--wasm-ld-path={}", + self.config + .sysroot_base + .join("lib/rustlib") + .join(&self.config.host) + .join("bin/gcc-ld/wasm-ld") + )); + } else { + aux_props.force_host = true; + } } let mut aux_dir = aux_dir.to_path_buf(); if aux_type == Some(AuxType::Bin) { @@ -1576,6 +1596,11 @@ impl<'test> TestCx<'test> { }; compiler.arg(input_file); + // Enable wasm proc macros. + if self.config.wasm_proc_macros { + compiler.arg("-Zwasm-proc-macros"); + } + // Hide libstd sources from ui tests to make sure we generate the stderr // output that users will see. // Without this, we may be producing good diagnostics in-tree but users diff --git a/src/tools/compiletest/src/rustdoc_gui_test.rs b/src/tools/compiletest/src/rustdoc_gui_test.rs index 7fc37a1a4371d..88880f09837ee 100644 --- a/src/tools/compiletest/src/rustdoc_gui_test.rs +++ b/src/tools/compiletest/src/rustdoc_gui_test.rs @@ -143,5 +143,6 @@ fn incomplete_config_for_rustdoc_gui_test() -> Config { jobs: Default::default(), parallel_frontend_threads: Config::DEFAULT_PARALLEL_FRONTEND_THREADS, iteration_count: Config::DEFAULT_ITERATION_COUNT, + wasm_proc_macros: false, } }