Skip to content

Commit b310eb9

Browse files
authored
Rollup merge of rust-lang#146457 - alexcrichton:wasm-no-exn-instructions, r=bjorn3
Skip cleanups on unsupported targets This commit is an update to the `AbortUnwindingCalls` MIR pass in the compiler. Specifically a new boolean is added for "can this target possibly unwind" and if that's `false` then terminators are all adjusted to be unreachable/not present. The end result is that this fixes rust-lang#140293 for wasm targets. The motivation for this PR is that currently on WebAssembly targets the usage of the `C-unwind` ABI can lead LLVM to either (a) emit exception-handling instructions or (b) hit a LLVM-ICE-style codegen error. WebAssembly as a base instruction set does not support unwinding at all, and a later proposal to WebAssembly, the exception-handling proposal, was what enabled this. This means that the current intent of WebAssembly targets is that they maintain the baseline of "don't emit exception-handling instructions unless enabled". The commit here is intended to restore this behavior by skipping these instructions even when `C-unwind` is present. Exception-handling is a relatively tricky and also murky topic in WebAssembly, however. There are two sets of instructions LLVM can emit for WebAssembly exceptions, Rust's Emscripten target supports exceptions, WASI targets do not, the LLVM flags to enable this are not always obvious, and additionally this all touches on "changing exception-handling behavior should be a target-level concern, not a feature". Effectively WebAssembly's exception-handling integration into Rust is not finalized at this time. The best idea at this time is that a parallel set of targets will eventually be added which support exceptions, but it's not clear if/when to do this. In the meantime the goal is to keep existing targets working while still enabling experimentation with exception-handling with `-Zbuild-std` and various permutations of LLVM flags. To that extent this commit does not blanket disable these landing pads and cleanup routines for WebAssembly but instead checks to see if panic=unwind is enabled or if `+exception-handling` is enabled. Tests are updated here as well to account for this where, by default, using a `C-unwind` ABI won't affect Rust codegen at all. If `+exception-handling` is enabled, however, then Rust codegen will look like native platforms where exceptions are caught and the program aborts. More-or-less I've done my best to keep exceptions working on wasm where it's possible to have them work, but turned them off where they're not supposed to be emitted. Closes rust-lang#140293
2 parents c3e118b + 88d7d20 commit b310eb9

File tree

5 files changed

+86
-27
lines changed

5 files changed

+86
-27
lines changed

compiler/rustc_mir_transform/src/abort_unwinding_calls.rs

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use rustc_ast::InlineAsmOptions;
33
use rustc_middle::mir::*;
44
use rustc_middle::span_bug;
55
use rustc_middle::ty::{self, TyCtxt, layout};
6+
use rustc_span::sym;
67
use rustc_target::spec::PanicStrategy;
78

89
/// A pass that runs which is targeted at ensuring that codegen guarantees about
@@ -33,6 +34,19 @@ impl<'tcx> crate::MirPass<'tcx> for AbortUnwindingCalls {
3334
return;
3435
}
3536

37+
// Represent whether this compilation target fundamentally doesn't
38+
// support unwinding at all at an ABI level. If this the target has no
39+
// support for unwinding then cleanup actions, for example, are all
40+
// unnecessary and can be considered unreachable.
41+
//
42+
// Currently this is only true for wasm targets on panic=abort when the
43+
// `exception-handling` target feature is disabled. In such a
44+
// configuration it's illegal to emit exception-related instructions so
45+
// it's not possible to unwind.
46+
let target_supports_unwinding = !(tcx.sess.target.is_like_wasm
47+
&& tcx.sess.panic_strategy() == PanicStrategy::Abort
48+
&& !tcx.asm_target_features(def_id).contains(&sym::exception_handling));
49+
3650
// Here we test for this function itself whether its ABI allows
3751
// unwinding or not.
3852
let body_ty = tcx.type_of(def_id).skip_binder();
@@ -54,12 +68,18 @@ impl<'tcx> crate::MirPass<'tcx> for AbortUnwindingCalls {
5468
let Some(terminator) = &mut block.terminator else { continue };
5569
let span = terminator.source_info.span;
5670

57-
// If we see an `UnwindResume` terminator inside a function that cannot unwind, we need
58-
// to replace it with `UnwindTerminate`.
59-
if let TerminatorKind::UnwindResume = &terminator.kind
60-
&& !body_can_unwind
61-
{
62-
terminator.kind = TerminatorKind::UnwindTerminate(UnwindTerminateReason::Abi);
71+
// If we see an `UnwindResume` terminator inside a function then:
72+
//
73+
// * If the target doesn't support unwinding at all, then this is an
74+
// unreachable block.
75+
// * If the body cannot unwind, we need to replace it with
76+
// `UnwindTerminate`.
77+
if let TerminatorKind::UnwindResume = &terminator.kind {
78+
if !target_supports_unwinding {
79+
terminator.kind = TerminatorKind::Unreachable;
80+
} else if !body_can_unwind {
81+
terminator.kind = TerminatorKind::UnwindTerminate(UnwindTerminateReason::Abi);
82+
}
6383
}
6484

6585
if block.is_cleanup {
@@ -93,8 +113,9 @@ impl<'tcx> crate::MirPass<'tcx> for AbortUnwindingCalls {
93113
_ => continue,
94114
};
95115

96-
if !call_can_unwind {
97-
// If this function call can't unwind, then there's no need for it
116+
if !call_can_unwind || !target_supports_unwinding {
117+
// If this function call can't unwind, or if the target doesn't
118+
// support unwinding at all, then there's no need for it
98119
// to have a landing pad. This means that we can remove any cleanup
99120
// registered for it (and turn it into `UnwindAction::Unreachable`).
100121
let cleanup = block.terminator_mut().unwind_mut().unwrap();

compiler/rustc_span/src/symbol.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,7 @@ symbols! {
940940
ermsb_target_feature,
941941
exact_div,
942942
except,
943+
exception_handling: "exception-handling",
943944
exchange_malloc,
944945
exclusive_range_pattern,
945946
exhaustive_integer_patterns,

tests/codegen-llvm/unwind-abis/c-unwind-abi-panic-abort.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
//@ compile-flags: -C panic=abort
2+
//@ revisions: NONWASM WASM WASMEXN
3+
//@ [NONWASM] ignore-wasm32
4+
//@ [WASM] only-wasm32
5+
//@ [WASMEXN] only-wasm32
6+
//@ [WASMEXN] compile-flags: -Ctarget-feature=+exception-handling
27

38
// Test that `nounwind` attributes are also applied to extern `C-unwind` Rust functions
49
// when the code is compiled with `panic=abort`.
@@ -9,7 +14,9 @@
914
#[no_mangle]
1015
pub unsafe extern "C-unwind" fn rust_item_that_can_unwind() {
1116
// Handle both legacy and v0 symbol mangling.
12-
// CHECK: call void @{{.*core9panicking19panic_cannot_unwind}}
17+
// NONWASM: call void @{{.*core9panicking19panic_cannot_unwind}}
18+
// WASMEXN: call void @{{.*core9panicking19panic_cannot_unwind}}
19+
// WASM-NOT: call void @{{.*core9panicking19panic_cannot_unwind}}
1320
may_unwind();
1421
}
1522

tests/codegen-llvm/unwind-and-panic-abort.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
//@ compile-flags: -C panic=abort
2+
//@ revisions: NONWASM WASM WASMEXN
3+
//@ [NONWASM] ignore-wasm32
4+
//@ [WASM] only-wasm32
5+
//@ [WASMEXN] only-wasm32
6+
//@ [WASMEXN] compile-flags: -Ctarget-feature=+exception-handling
27

38
#![crate_type = "lib"]
49

@@ -9,7 +14,9 @@ extern "C-unwind" {
914
// CHECK: Function Attrs:{{.*}}nounwind
1015
// CHECK-NEXT: define{{.*}}void @foo
1116
// Handle both legacy and v0 symbol mangling.
12-
// CHECK: call void @{{.*core9panicking19panic_cannot_unwind}}
17+
// NONWASM: call void @{{.*core9panicking19panic_cannot_unwind}}
18+
// WASMEXN: call void @{{.*core9panicking19panic_cannot_unwind}}
19+
// WASM-NOT: call void @{{.*core9panicking19panic_cannot_unwind}}
1320
#[no_mangle]
1421
pub unsafe extern "C" fn foo() {
1522
bar();

tests/codegen-llvm/wasm_exceptions.rs

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
//@ only-wasm32
2-
//@ compile-flags: -C panic=unwind -Z emscripten-wasm-eh
2+
//@ revisions: WASM WASMEXN
3+
//@ [WASMEXN] compile-flags: -C panic=unwind -Z emscripten-wasm-eh
34

45
#![crate_type = "lib"]
5-
#![feature(core_intrinsics, wasm_exception_handling_intrinsics)]
6+
#![feature(core_intrinsics, wasm_exception_handling_intrinsics, link_llvm_intrinsics)]
67

78
extern "C-unwind" {
89
fn may_panic();
@@ -22,20 +23,25 @@ impl Drop for LogOnDrop {
2223
}
2324
}
2425

25-
// CHECK-LABEL: @test_cleanup() {{.*}} @__gxx_wasm_personality_v0
26+
// WASM-LABEL: @test_cleanup() {{.*}}
27+
// WASMEXN-LABEL: @test_cleanup() {{.*}} @__gxx_wasm_personality_v0
2628
#[no_mangle]
2729
pub fn test_cleanup() {
2830
let _log_on_drop = LogOnDrop;
2931
unsafe {
3032
may_panic();
3133
}
3234

33-
// CHECK-NOT: call
34-
// CHECK: invoke void @may_panic()
35-
// CHECK: %cleanuppad = cleanuppad within none []
35+
// WASMEXN-NOT: call
36+
// WASMEXN: invoke void @may_panic()
37+
// WASMEXN: %cleanuppad = cleanuppad within none []
38+
//
39+
// WASM: call void @may_panic()
40+
// WASM-NOT: invoke void @may_panic()
3641
}
3742

38-
// CHECK-LABEL: @test_rtry() {{.*}} @__gxx_wasm_personality_v0
43+
// WASM-LABEL: @test_rtry() {{.*}}
44+
// WASMEXN-LABEL: @test_rtry() {{.*}} @__gxx_wasm_personality_v0
3945
#[no_mangle]
4046
pub fn test_rtry() {
4147
unsafe {
@@ -51,23 +57,40 @@ pub fn test_rtry() {
5157
);
5258
}
5359

54-
// CHECK-NOT: call
55-
// CHECK: invoke void @may_panic()
56-
// CHECK: {{.*}} = catchswitch within none [label {{.*}}] unwind to caller
57-
// CHECK: {{.*}} = catchpad within {{.*}} [ptr null]
58-
// CHECK: catchret
60+
// WASMEXN-NOT: call
61+
// WASMEXN: invoke void @may_panic()
62+
// WASMEXN: {{.*}} = catchswitch within none [label {{.*}}] unwind to caller
63+
// WASMEXN: {{.*}} = catchpad within {{.*}} [ptr null]
64+
// WASMEXN: catchret
65+
66+
// WASM: call void @may_panic()
67+
// WASM-NOT: invoke void @may_panic()
68+
// WASM-NOT: catchswitch
69+
// WASM-NOT: catchpad
70+
// WASM-NOT: catchret
5971
}
6072

6173
// Make sure the intrinsic is not inferred as nounwind. This is a regression test for #132416.
62-
// CHECK-LABEL: @test_intrinsic() {{.*}} @__gxx_wasm_personality_v0
74+
//
75+
// Note that this test uses the raw `wasm_throw` intrinsic because the one from
76+
// libstd was built with `-Cpanic=abort` and it's technically not valid to use
77+
// when this crate is compiled with `-Cpanic=unwind`.
78+
//
79+
// WASMEXN-LABEL: @test_intrinsic() {{.*}} @__gxx_wasm_personality_v0
6380
#[no_mangle]
81+
#[cfg(wasmexn)]
6482
pub fn test_intrinsic() {
6583
let _log_on_drop = LogOnDrop;
84+
85+
unsafe extern "C-unwind" {
86+
#[link_name = "llvm.wasm.throw"]
87+
fn wasm_throw(tag: i32, ptr: *mut u8) -> !;
88+
}
6689
unsafe {
67-
core::arch::wasm32::throw::<0>(core::ptr::null_mut());
90+
wasm_throw(0, core::ptr::null_mut());
6891
}
6992

70-
// CHECK-NOT: call
71-
// CHECK: invoke void @llvm.wasm.throw(i32 noundef 0, ptr noundef null)
72-
// CHECK: %cleanuppad = cleanuppad within none []
93+
// WASMEXN-NOT: call
94+
// WASMEXN: invoke void @llvm.wasm.throw(i32 noundef 0, ptr noundef null)
95+
// WASMEXN: %cleanuppad = cleanuppad within none []
7396
}

0 commit comments

Comments
 (0)