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
12 changes: 12 additions & 0 deletions bootstrap.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,18 @@
# because bootstrap will attempt to download the JSON docs data for this commit from its CI.
#rust.stdlib-semver-baseline = "<commit-sha>"

# 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
#
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_interface/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down
6 changes: 6 additions & 0 deletions compiler/rustc_interface/src/passes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

@bjorn3 bjorn3 Aug 11, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't have to be in this PR, but we should suppress the panic=abort warning below for wasm proc-macros.

View changes since the review

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I intentionally left that out to keep this more minimal and not really changing behavior (just plumbing).


if crate_types.contains(&CrateType::Sdylib) && !tcx.features().export_stable() {
feature_err(sess, sym::export_stable, DUMMY_SP, "`sdylib` crate type is unstable").emit();
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_interface/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_session/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions src/bootstrap/src/core/build_steps/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/bootstrap/src/core/config/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/bootstrap/src/core/config/toml/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ define_config! {
break_on_ice: Option<bool> = "break-on-ice",
parallel_frontend_threads: Option<u32> = "parallel-frontend-threads",
stdlib_semver_baseline: Option<String> = "stdlib-semver-baseline",
wasm_proc_macros: Option<bool> = "wasm-proc-macros",
}
}

Expand Down Expand Up @@ -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:
Expand Down
11 changes: 9 additions & 2 deletions src/bootstrap/src/utils/cc_detect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 { .. }
Expand All @@ -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);
}
}
Expand Down
8 changes: 8 additions & 0 deletions src/doc/unstable-book/src/compiler-flags/wasm-proc-macros.md
Original file line number Diff line number Diff line change
@@ -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.
6 changes: 6 additions & 0 deletions src/tools/compiletest/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -503,6 +507,8 @@ pub(crate) fn parse_config(args: Vec<String>) -> Config {

gcc_supported_target_tuples,

wasm_proc_macros: args.wasm_proc_macros,

jobs: args.jobs,

parallel_frontend_threads,
Expand Down
2 changes: 2 additions & 0 deletions src/tools/compiletest/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions src/tools/compiletest/src/directives/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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".
Expand Down
2 changes: 2 additions & 0 deletions src/tools/compiletest/src/directives/directive_names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
27 changes: 26 additions & 1 deletion src/tools/compiletest/src/runtest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.

@bjorn3 bjorn3 Aug 10, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jieyouxu do you know if there is a better way to force usage of the default linker or at least use the linker set for the wasm32-wasip2 target rather than the target that gets tested?

View changes since the review

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I'm afraid I don't recall existing mechanisms to force reusing the "host" linker for the target-being-tested (cross-compile in compiletest was always a bit fishy)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tracking the general problem in #160917.

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) {
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/tools/compiletest/src/rustdoc_gui_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
Loading