Skip to content

Commit be055d9

Browse files
committed
Auto merge of #67502 - Mark-Simulacrum:opt-catch, r=Mark-Simulacrum
Optimize catch_unwind to match C++ try/catch This refactors the implementation of catching unwinds to allow LLVM to inline the "try" closure directly into the happy path, avoiding indirection. This means that the catch_unwind implementation is (after this PR) zero-cost unless a panic is thrown. https://rust.godbolt.org/z/cZcUSB is an example of the current codegen in a simple case. Notably, the codegen is *exactly the same* if `-Cpanic=abort` is passed, which is clearly not great. This PR, on the other hand, generates the following assembly: ```asm # -Cpanic=unwind: push rbx mov ebx,0x2a call QWORD PTR [rip+0x1c53c] # <happy> mov eax,ebx pop rbx ret mov rdi,rax call QWORD PTR [rip+0x1c537] # cleanup function call call QWORD PTR [rip+0x1c539] # <unfortunate> mov ebx,0xd mov eax,ebx pop rbx ret # -Cpanic=abort: push rax call QWORD PTR [rip+0x20a1] # <happy> mov eax,0x2a pop rcx ret ``` Fixes #64224, and resolves #64222.
2 parents 1572c43 + 9f3679f commit be055d9

File tree

39 files changed

+368
-370
lines changed

39 files changed

+368
-370
lines changed

src/doc/unstable-book/src/language-features/lang-items.md

+3-20
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ fn main(_argc: isize, _argv: *const *const u8) -> isize {
5252
5353
#[lang = "eh_personality"] extern fn rust_eh_personality() {}
5454
#[lang = "panic_impl"] extern fn rust_begin_panic(info: &PanicInfo) -> ! { unsafe { intrinsics::abort() } }
55-
#[lang = "eh_unwind_resume"] extern fn rust_eh_unwind_resume() {}
5655
#[no_mangle] pub extern fn rust_eh_register_frames () {}
5756
#[no_mangle] pub extern fn rust_eh_unregister_frames () {}
5857
```
@@ -67,7 +66,7 @@ Other features provided by lang items include:
6766
marked with lang items; those specific four are `eq`, `ord`,
6867
`deref`, and `add` respectively.
6968
- stack unwinding and general failure; the `eh_personality`,
70-
`eh_unwind_resume`, `fail` and `fail_bounds_checks` lang items.
69+
`panic` and `panic_bounds_checks` lang items.
7170
- the traits in `std::marker` used to indicate types of
7271
various kinds; lang items `send`, `sync` and `copy`.
7372
- the marker types and variance indicators found in
@@ -130,12 +129,6 @@ fn start(_argc: isize, _argv: *const *const u8) -> isize {
130129
pub extern fn rust_eh_personality() {
131130
}
132131
133-
// This function may be needed based on the compilation target.
134-
#[lang = "eh_unwind_resume"]
135-
#[no_mangle]
136-
pub extern fn rust_eh_unwind_resume() {
137-
}
138-
139132
#[lang = "panic_impl"]
140133
#[no_mangle]
141134
pub extern fn rust_begin_panic(info: &PanicInfo) -> ! {
@@ -173,12 +166,6 @@ pub extern fn main(_argc: i32, _argv: *const *const u8) -> i32 {
173166
pub extern fn rust_eh_personality() {
174167
}
175168
176-
// This function may be needed based on the compilation target.
177-
#[lang = "eh_unwind_resume"]
178-
#[no_mangle]
179-
pub extern fn rust_eh_unwind_resume() {
180-
}
181-
182169
#[lang = "panic_impl"]
183170
#[no_mangle]
184171
pub extern fn rust_begin_panic(info: &PanicInfo) -> ! {
@@ -211,10 +198,8 @@ compiler. When a panic happens, this controls the message that's displayed on
211198
the screen. While the language item's name is `panic_impl`, the symbol name is
212199
`rust_begin_panic`.
213200

214-
A third function, `rust_eh_unwind_resume`, is also needed if the `custom_unwind_resume`
215-
flag is set in the options of the compilation target. It allows customizing the
216-
process of resuming unwind at the end of the landing pads. The language item's name
217-
is `eh_unwind_resume`.
201+
Finally, a `eh_catch_typeinfo` static is needed for certain targets which
202+
implement Rust panics on top of C++ exceptions.
218203

219204
## List of all language items
220205

@@ -247,8 +232,6 @@ the source code.
247232
- `eh_personality`: `libpanic_unwind/emcc.rs` (EMCC)
248233
- `eh_personality`: `libpanic_unwind/gcc.rs` (GNU)
249234
- `eh_personality`: `libpanic_unwind/seh.rs` (SEH)
250-
- `eh_unwind_resume`: `libpanic_unwind/gcc.rs` (GCC)
251-
- `eh_catch_typeinfo`: `libpanic_unwind/seh.rs` (SEH)
252235
- `eh_catch_typeinfo`: `libpanic_unwind/emcc.rs` (EMCC)
253236
- `panic`: `libcore/panicking.rs`
254237
- `panic_bounds_check`: `libcore/panicking.rs`

src/libcore/intrinsics.rs

+8-6
Original file line numberDiff line numberDiff line change
@@ -1871,14 +1871,16 @@ extern "rust-intrinsic" {
18711871
#[rustc_const_unstable(feature = "const_discriminant", issue = "69821")]
18721872
pub fn discriminant_value<T>(v: &T) -> u64;
18731873

1874-
/// Rust's "try catch" construct which invokes the function pointer `f` with
1875-
/// the data pointer `data`.
1874+
/// Rust's "try catch" construct which invokes the function pointer `try_fn`
1875+
/// with the data pointer `data`.
18761876
///
1877-
/// The third pointer is a target-specific data pointer which is filled in
1878-
/// with the specifics of the exception that occurred. For examples on Unix
1879-
/// platforms this is a `*mut *mut T` which is filled in by the compiler and
1880-
/// on MSVC it's `*mut [usize; 2]`. For more information see the compiler's
1877+
/// The third argument is a function called if a panic occurs. This function
1878+
/// takes the data pointer and a pointer to the target-specific exception
1879+
/// object that was caught. For more information see the compiler's
18811880
/// source as well as std's catch implementation.
1881+
#[cfg(not(bootstrap))]
1882+
pub fn r#try(try_fn: fn(*mut u8), data: *mut u8, catch_fn: fn(*mut u8, *mut u8)) -> i32;
1883+
#[cfg(bootstrap)]
18821884
pub fn r#try(f: fn(*mut u8), data: *mut u8, local_ptr: *mut u8) -> i32;
18831885

18841886
/// Emits a `!nontemporal` store according to LLVM (see their docs).

src/libpanic_abort/lib.rs

+10-16
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,11 @@
1818
#![feature(staged_api)]
1919
#![feature(rustc_attrs)]
2020

21-
// Rust's "try" function, but if we're aborting on panics we just call the
22-
// function as there's nothing else we need to do here.
21+
use core::any::Any;
22+
2323
#[rustc_std_internal_symbol]
24-
pub unsafe extern "C" fn __rust_maybe_catch_panic(
25-
f: fn(*mut u8),
26-
data: *mut u8,
27-
_data_ptr: *mut usize,
28-
_vtable_ptr: *mut usize,
29-
) -> u32 {
30-
f(data);
31-
0
24+
pub unsafe extern "C" fn __rust_panic_cleanup(_: *mut u8) -> *mut (dyn Any + Send + 'static) {
25+
unreachable!()
3226
}
3327

3428
// "Leak" the payload and shim to the relevant abort on the platform in
@@ -92,7 +86,7 @@ pub unsafe extern "C" fn __rust_start_panic(_payload: usize) -> u32 {
9286
// binaries, but it should never be called as we don't link in an unwinding
9387
// runtime at all.
9488
pub mod personalities {
95-
#[no_mangle]
89+
#[rustc_std_internal_symbol]
9690
#[cfg(not(any(
9791
all(target_arch = "wasm32", not(target_os = "emscripten"),),
9892
all(target_os = "windows", target_env = "gnu", target_arch = "x86_64",),
@@ -101,7 +95,7 @@ pub mod personalities {
10195

10296
// On x86_64-pc-windows-gnu we use our own personality function that needs
10397
// to return `ExceptionContinueSearch` as we're passing on all our frames.
104-
#[no_mangle]
98+
#[rustc_std_internal_symbol]
10599
#[cfg(all(target_os = "windows", target_env = "gnu", target_arch = "x86_64"))]
106100
pub extern "C" fn rust_eh_personality(
107101
_record: usize,
@@ -117,16 +111,16 @@ pub mod personalities {
117111
//
118112
// Note that we don't execute landing pads, so this is never called, so it's
119113
// body is empty.
120-
#[no_mangle]
121-
#[cfg(all(target_os = "windows", target_env = "gnu"))]
114+
#[rustc_std_internal_symbol]
115+
#[cfg(all(bootstrap, target_os = "windows", target_env = "gnu"))]
122116
pub extern "C" fn rust_eh_unwind_resume() {}
123117

124118
// These two are called by our startup objects on i686-pc-windows-gnu, but
125119
// they don't need to do anything so the bodies are nops.
126-
#[no_mangle]
120+
#[rustc_std_internal_symbol]
127121
#[cfg(all(target_os = "windows", target_env = "gnu", target_arch = "x86"))]
128122
pub extern "C" fn rust_eh_register_frames() {}
129-
#[no_mangle]
123+
#[rustc_std_internal_symbol]
130124
#[cfg(all(target_os = "windows", target_env = "gnu", target_arch = "x86"))]
131125
pub extern "C" fn rust_eh_unregister_frames() {}
132126
}

src/libpanic_unwind/dummy.rs

-4
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,6 @@ use alloc::boxed::Box;
66
use core::any::Any;
77
use core::intrinsics;
88

9-
pub fn payload() -> *mut u8 {
10-
core::ptr::null_mut()
11-
}
12-
139
pub unsafe fn cleanup(_ptr: *mut u8) -> Box<dyn Any + Send> {
1410
intrinsics::abort()
1511
}

src/libpanic_unwind/emcc.rs

+1-6
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,11 @@ static EXCEPTION_TYPE_INFO: TypeInfo = TypeInfo {
4848
name: b"rust_panic\0".as_ptr(),
4949
};
5050

51-
pub fn payload() -> *mut u8 {
52-
ptr::null_mut()
53-
}
54-
5551
struct Exception {
5652
// This needs to be an Option because the object's lifetime follows C++
5753
// semantics: when catch_unwind moves the Box out of the exception it must
5854
// still leave the exception object in a valid state because its destructor
59-
// is still going to be called by __cxa_end_catch..
55+
// is still going to be called by __cxa_end_catch.
6056
data: Option<Box<dyn Any + Send>>,
6157
}
6258

@@ -98,7 +94,6 @@ extern "C" fn exception_cleanup(ptr: *mut libc::c_void) -> DestructorRet {
9894
}
9995

10096
#[lang = "eh_personality"]
101-
#[no_mangle]
10297
unsafe extern "C" fn rust_eh_personality(
10398
version: c_int,
10499
actions: uw::_Unwind_Action,

src/libpanic_unwind/gcc.rs

+3-19
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,11 @@
3535
//!
3636
//! Once stack has been unwound down to the handler frame level, unwinding stops
3737
//! and the last personality routine transfers control to the catch block.
38-
//!
39-
//! ## `eh_personality` and `eh_unwind_resume`
40-
//!
41-
//! These language items are used by the compiler when generating unwind info.
42-
//! The first one is the personality routine described above. The second one
43-
//! allows compilation target to customize the process of resuming unwind at the
44-
//! end of the landing pads. `eh_unwind_resume` is used only if
45-
//! `custom_unwind_resume` flag in the target options is set.
4638
4739
#![allow(private_no_mangle_fns)]
4840

4941
use alloc::boxed::Box;
5042
use core::any::Any;
51-
use core::ptr;
5243

5344
use crate::dwarf::eh::{self, EHAction, EHContext};
5445
use libc::{c_int, uintptr_t};
@@ -83,10 +74,6 @@ pub unsafe fn panic(data: Box<dyn Any + Send>) -> u32 {
8374
}
8475
}
8576

86-
pub fn payload() -> *mut u8 {
87-
ptr::null_mut()
88-
}
89-
9077
pub unsafe fn cleanup(ptr: *mut u8) -> Box<dyn Any + Send> {
9178
let exception = Box::from_raw(ptr as *mut Exception);
9279
exception.cause
@@ -143,7 +130,6 @@ cfg_if::cfg_if! {
143130
//
144131
// iOS uses the default routine instead since it uses SjLj unwinding.
145132
#[lang = "eh_personality"]
146-
#[no_mangle]
147133
unsafe extern "C" fn rust_eh_personality(state: uw::_Unwind_State,
148134
exception_object: *mut uw::_Unwind_Exception,
149135
context: *mut uw::_Unwind_Context)
@@ -277,7 +263,6 @@ cfg_if::cfg_if! {
277263
// On x86_64 MinGW targets, the unwinding mechanism is SEH however the unwind
278264
// handler data (aka LSDA) uses GCC-compatible encoding.
279265
#[lang = "eh_personality"]
280-
#[no_mangle]
281266
#[allow(nonstandard_style)]
282267
unsafe extern "C" fn rust_eh_personality(exceptionRecord: *mut uw::EXCEPTION_RECORD,
283268
establisherFrame: uw::LPVOID,
@@ -293,7 +278,6 @@ cfg_if::cfg_if! {
293278
} else {
294279
// The personality routine for most of our targets.
295280
#[lang = "eh_personality"]
296-
#[no_mangle]
297281
unsafe extern "C" fn rust_eh_personality(version: c_int,
298282
actions: uw::_Unwind_Action,
299283
exception_class: uw::_Unwind_Exception_Class,
@@ -329,8 +313,8 @@ unsafe fn find_eh_action(
329313
eh::find_eh_action(lsda, &eh_context, foreign_exception)
330314
}
331315

332-
// See docs in the `unwind` module.
333316
#[cfg(all(
317+
bootstrap,
334318
target_os = "windows",
335319
any(target_arch = "x86", target_arch = "x86_64"),
336320
target_env = "gnu"
@@ -364,12 +348,12 @@ pub mod eh_frame_registry {
364348
fn __deregister_frame_info(eh_frame_begin: *const u8, object: *mut u8);
365349
}
366350

367-
#[no_mangle]
351+
#[rustc_std_internal_symbol]
368352
pub unsafe extern "C" fn rust_eh_register_frames(eh_frame_begin: *const u8, object: *mut u8) {
369353
__register_frame_info(eh_frame_begin, object);
370354
}
371355

372-
#[no_mangle]
356+
#[rustc_std_internal_symbol]
373357
pub unsafe extern "C" fn rust_eh_unregister_frames(eh_frame_begin: *const u8, object: *mut u8) {
374358
__deregister_frame_info(eh_frame_begin, object);
375359
}

src/libpanic_unwind/hermit.rs

-4
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,6 @@ use alloc::boxed::Box;
66
use core::any::Any;
77
use core::ptr;
88

9-
pub fn payload() -> *mut u8 {
10-
ptr::null_mut()
11-
}
12-
139
pub unsafe fn cleanup(_ptr: *mut u8) -> Box<dyn Any + Send> {
1410
extern "C" {
1511
pub fn __rust_abort() -> !;

src/libpanic_unwind/lib.rs

+7-27
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,18 @@
2222
#![feature(libc)]
2323
#![feature(nll)]
2424
#![feature(panic_unwind)]
25-
#![feature(raw)]
2625
#![feature(staged_api)]
2726
#![feature(std_internals)]
2827
#![feature(unwind_attributes)]
2928
#![feature(abi_thiscall)]
29+
#![feature(rustc_attrs)]
30+
#![feature(raw)]
3031
#![panic_runtime]
3132
#![feature(panic_runtime)]
3233

3334
use alloc::boxed::Box;
34-
use core::intrinsics;
35-
use core::mem;
35+
use core::any::Any;
3636
use core::panic::BoxMeUp;
37-
use core::raw;
3837

3938
cfg_if::cfg_if! {
4039
if #[cfg(target_os = "emscripten")] {
@@ -69,33 +68,14 @@ extern "C" {
6968

7069
mod dwarf;
7170

72-
// Entry point for catching an exception, implemented using the `try` intrinsic
73-
// in the compiler.
74-
//
75-
// The interaction between the `payload` function and the compiler is pretty
76-
// hairy and tightly coupled, for more information see the compiler's
77-
// implementation of this.
78-
#[no_mangle]
79-
pub unsafe extern "C" fn __rust_maybe_catch_panic(
80-
f: fn(*mut u8),
81-
data: *mut u8,
82-
data_ptr: *mut usize,
83-
vtable_ptr: *mut usize,
84-
) -> u32 {
85-
let mut payload = imp::payload();
86-
if intrinsics::r#try(f, data, &mut payload as *mut _ as *mut _) == 0 {
87-
0
88-
} else {
89-
let obj = mem::transmute::<_, raw::TraitObject>(imp::cleanup(payload));
90-
*data_ptr = obj.data as usize;
91-
*vtable_ptr = obj.vtable as usize;
92-
1
93-
}
71+
#[rustc_std_internal_symbol]
72+
pub unsafe extern "C" fn __rust_panic_cleanup(payload: *mut u8) -> *mut (dyn Any + Send + 'static) {
73+
Box::into_raw(imp::cleanup(payload))
9474
}
9575

9676
// Entry point for raising an exception, just delegates to the platform-specific
9777
// implementation.
98-
#[no_mangle]
78+
#[rustc_std_internal_symbol]
9979
#[unwind(allowed)]
10080
pub unsafe extern "C" fn __rust_start_panic(payload: usize) -> u32 {
10181
let payload = payload as *mut &mut dyn BoxMeUp;

0 commit comments

Comments
 (0)