Rollup merge of #156554 - guybedford:wasm-use-legacy-eh, r=alexcrichton

Allow user-provided `llvm_args` to override target spec arguments

This switches the order in which `-Cllvm-args` is applied between target-spec arguments and user-provided LLVM arguments.

This came up in rust-lang/rust#156061, where the target passing `-Cllvm-args=-wasm-use-legacy-eh=false` means that a user passing `-Cllvm-args=-wasm-use-legacy-eh=true` cannot override this value since the LLVM arguments support the last argument overriding the previous, and user arguments were chained first.

With this change, it is possible for Wasm targets to opt into legacy EH for compatibility with runtimes that don't yet implement the modern exnref/try_table instructions, such as Node.js 20 on V8 11.3 and older browsers. While Node.js 20 is formally EOL, many libraries will still need to support this version for a few months yet, so this would ease the transition path to modern exception handling having an opt-out.

Originally this PR added support for a dedicated `-Z` flag for switching to legacy exception handling, but fine-grained control over the arguments would be a preferable solution provided it does not conflict with other behaviours.

//cc @alexcrichton
This commit is contained in:
Jonathan Brouwer
2026-05-15 20:11:43 +02:00
committed by GitHub
2 changed files with 84 additions and 1 deletions
+4 -1
View File
@@ -66,7 +66,10 @@ fn llvm_arg_to_arg_name(full_arg: &str) -> &str {
let cg_opts = sess.opts.cg.llvm_args.iter().map(AsRef::as_ref);
let tg_opts = sess.target.llvm_args.iter().map(AsRef::as_ref);
let sess_args = cg_opts.chain(tg_opts);
// Target-spec args are passed to LLVM before user `-Cllvm-args`. LLVM's
// `cl::opt` parser is last-wins, so this lets `-Cllvm-args=...` override
// a value already set in the target spec (e.g. `-wasm-use-legacy-eh`).
let sess_args = tg_opts.chain(cg_opts);
let user_specified_args: FxHashSet<_> =
sess_args.clone().map(|s| llvm_arg_to_arg_name(s)).filter(|s| !s.is_empty()).collect();
+80
View File
@@ -0,0 +1,80 @@
//@ only-wasm32
//@ assembly-output: emit-asm
//@ compile-flags: -C target-feature=+exception-handling
//@ compile-flags: -C panic=unwind
//@ compile-flags: -C llvm-args=-wasm-use-legacy-eh=true
// Verifies that user-supplied `-Cllvm-args` can override an LLVM argument that
// was set in the target spec. The `wasm32-unknown-unknown` target spec pins
// `-wasm-use-legacy-eh=false`; this test asserts that passing the opposite
// value via `-Cllvm-args` switches code generation back to the legacy
// exception-handling instructions.
#![crate_type = "lib"]
#![feature(core_intrinsics)]
extern "C-unwind" {
fn may_panic();
}
extern "C" {
fn log_number(number: usize);
}
struct LogOnDrop;
impl Drop for LogOnDrop {
fn drop(&mut self) {
unsafe {
log_number(0);
}
}
}
// CHECK-LABEL: test_cleanup:
#[no_mangle]
pub fn test_cleanup() {
let _log_on_drop = LogOnDrop;
unsafe {
may_panic();
}
// CHECK-NOT: try_table
// CHECK-NOT: catch_all_ref
// CHECK-NOT: throw_ref
// CHECK-NOT: call
// CHECK: try
// CHECK: call may_panic
// CHECK: catch_all
// CHECK: rethrow
// CHECK: end_try
}
// CHECK-LABEL: test_rtry:
#[no_mangle]
pub fn test_rtry() {
unsafe {
core::intrinsics::catch_unwind(
|_| {
may_panic();
},
core::ptr::null_mut(),
|data, exception| {
log_number(data as usize);
log_number(exception as usize);
},
);
}
// CHECK-NOT: try_table
// CHECK-NOT: catch_all_ref
// CHECK-NOT: throw_ref
// CHECK-NOT: call
// CHECK: try
// CHECK: call may_panic
// CHECK: catch
// CHECK: call log_number
// CHECK: call log_number
// CHECK-NOT: rethrow
// CHECK: end_try
}