Skip to content

Commit b14b8db

Browse files
authored
Rollup merge of rust-lang#67502 - Mark-Simulacrum:opt-catch, r=alexcrichton
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 rust-lang#64224, and resolves rust-lang#64222.
2 parents 1389494 + da5b1a4 commit b14b8db

File tree

16 files changed

+110
-107
lines changed

16 files changed

+110
-107
lines changed

Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -2301,6 +2301,7 @@ dependencies = [
23012301
name = "panic_abort"
23022302
version = "0.0.0"
23032303
dependencies = [
2304+
"cfg-if",
23042305
"compiler_builtins",
23052306
"core",
23062307
"libc",

src/libpanic_abort/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ doc = false
1414
core = { path = "../libcore" }
1515
libc = { version = "0.2", default-features = false }
1616
compiler_builtins = "0.1.0"
17+
cfg-if = "0.1.8"

src/libpanic_abort/lib.rs

+7-10
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,14 @@
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+
23+
// We need the definition of TryPayload for __rust_panic_cleanup.
24+
include!("../libpanic_unwind/payload.rs");
25+
2326
#[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
27+
pub unsafe extern "C" fn __rust_panic_cleanup(_: TryPayload) -> *mut (dyn Any + Send + 'static) {
28+
unreachable!()
3229
}
3330

3431
// "Leak" the payload and shim to the relevant abort on the platform in

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

-4
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,6 @@ 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
pub unsafe fn cleanup(ptr: *mut u8) -> Box<dyn Any + Send> {
5652
assert!(!ptr.is_null());
5753
let adjusted_ptr = __cxa_begin_catch(ptr as *mut libc::c_void);

src/libpanic_unwind/gcc.rs

-5
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848

4949
use alloc::boxed::Box;
5050
use core::any::Any;
51-
use core::ptr;
5251

5352
use crate::dwarf::eh::{self, EHAction, EHContext};
5453
use libc::{c_int, uintptr_t};
@@ -82,10 +81,6 @@ pub unsafe fn panic(data: Box<dyn Any + Send>) -> u32 {
8281
}
8382
}
8483

85-
pub fn payload() -> *mut u8 {
86-
ptr::null_mut()
87-
}
88-
8984
pub unsafe fn cleanup(ptr: *mut u8) -> Box<dyn Any + Send> {
9085
let my_ep = ptr as *mut Exception;
9186
let cause = (*my_ep).cause.take();

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

+11-25
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,20 @@
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)]
28+
#![feature(rustc_attrs)]
29+
#![feature(raw)]
2930
#![panic_runtime]
3031
#![feature(panic_runtime)]
3132

3233
use alloc::boxed::Box;
33-
use core::intrinsics;
34-
use core::mem;
34+
use core::any::Any;
3535
use core::panic::BoxMeUp;
36-
use core::raw;
3736

37+
// If adding to this list, you should also look at the list of TryPayload types
38+
// defined in payload.rs and likely add to there as well.
3839
cfg_if::cfg_if! {
3940
if #[cfg(target_os = "emscripten")] {
4041
#[path = "emcc.rs"]
@@ -60,30 +61,15 @@ cfg_if::cfg_if! {
6061
}
6162
}
6263

64+
include!("payload.rs");
65+
6366
mod dwarf;
6467

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

8975
// Entry point for raising an exception, just delegates to the platform-specific

src/libpanic_unwind/payload.rs

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Type definition for the payload argument of the try intrinsic.
2+
//
3+
// This must be kept in sync with the implementations of the try intrinsic.
4+
//
5+
// This file is included by both panic runtimes and libstd. It is part of the
6+
// panic runtime ABI.
7+
cfg_if::cfg_if! {
8+
if #[cfg(target_os = "emscripten")] {
9+
type TryPayload = *mut u8;
10+
} else if #[cfg(target_arch = "wasm32")] {
11+
type TryPayload = *mut u8;
12+
} else if #[cfg(target_os = "hermit")] {
13+
type TryPayload = *mut u8;
14+
} else if #[cfg(all(target_env = "msvc", target_arch = "aarch64"))] {
15+
type TryPayload = *mut u8;
16+
} else if #[cfg(target_env = "msvc")] {
17+
type TryPayload = [u64; 2];
18+
} else {
19+
type TryPayload = *mut u8;
20+
}
21+
}

src/libpanic_unwind/seh.rs

-4
Original file line numberDiff line numberDiff line change
@@ -264,10 +264,6 @@ pub unsafe fn panic(data: Box<dyn Any + Send>) -> u32 {
264264
_CxxThrowException(throw_ptr, &mut THROW_INFO as *mut _ as *mut _);
265265
}
266266

267-
pub fn payload() -> [u64; 2] {
268-
[0; 2]
269-
}
270-
271267
pub unsafe fn cleanup(payload: [u64; 2]) -> Box<dyn Any + Send> {
272268
mem::transmute(raw::TraitObject { data: payload[0] as *mut _, vtable: payload[1] as *mut _ })
273269
}

src/librustc_codegen_llvm/intrinsic.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -858,8 +858,10 @@ fn try_intrinsic(
858858
) {
859859
if bx.sess().no_landing_pads() {
860860
bx.call(func, &[data], None);
861-
let ptr_align = bx.tcx().data_layout.pointer_align.abi;
862-
bx.store(bx.const_null(bx.type_i8p()), dest, ptr_align);
861+
// Return 0 unconditionally from the intrinsic call;
862+
// we can never unwind.
863+
let ret_align = bx.tcx().data_layout.i32_align.abi;
864+
bx.store(bx.const_i32(0), dest, ret_align);
863865
} else if wants_msvc_seh(bx.sess()) {
864866
codegen_msvc_try(bx, func, data, local_ptr, dest);
865867
} else {

src/libstd/panicking.rs

+27-26
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@ use core::panic::{BoxMeUp, Location, PanicInfo};
1212
use crate::any::Any;
1313
use crate::fmt;
1414
use crate::intrinsics;
15-
use crate::mem::{self, ManuallyDrop};
15+
use crate::mem::{self, ManuallyDrop, MaybeUninit};
1616
use crate::process;
17-
use crate::raw;
1817
use crate::sync::atomic::{AtomicBool, Ordering};
1918
use crate::sys::stdio::panic_output;
2019
use crate::sys_common::backtrace::{self, RustBacktrace};
@@ -29,6 +28,9 @@ use crate::io::set_panic;
2928
#[cfg(test)]
3029
use realstd::io::set_panic;
3130

31+
// Include the definition of UnwindPayload from libpanic_unwind.
32+
include!("../libpanic_unwind/payload.rs");
33+
3234
// Binary interface to the panic runtime that the standard library depends on.
3335
//
3436
// The standard library is tagged with `#![needs_panic_runtime]` (introduced in
@@ -41,12 +43,9 @@ use realstd::io::set_panic;
4143
// hook up these functions, but it is not this day!
4244
#[allow(improper_ctypes)]
4345
extern "C" {
44-
fn __rust_maybe_catch_panic(
45-
f: fn(*mut u8),
46-
data: *mut u8,
47-
data_ptr: *mut usize,
48-
vtable_ptr: *mut usize,
49-
) -> u32;
46+
/// The payload ptr here is actually the same as the payload ptr for the try
47+
/// intrinsic (i.e., is really `*mut [u64; 2]` or `*mut *mut u8`).
48+
fn __rust_panic_cleanup(payload: TryPayload) -> *mut (dyn Any + Send + 'static);
5049

5150
/// `payload` is actually a `*mut &mut dyn BoxMeUp` but that would cause FFI warnings.
5251
/// It cannot be `Box<dyn BoxMeUp>` because the other end of this call does not depend
@@ -241,9 +240,9 @@ pub unsafe fn r#try<R, F: FnOnce() -> R>(f: F) -> Result<R, Box<dyn Any + Send>>
241240
}
242241

243242
// We do some sketchy operations with ownership here for the sake of
244-
// performance. We can only pass pointers down to
245-
// `__rust_maybe_catch_panic` (can't pass objects by value), so we do all
246-
// the ownership tracking here manually using a union.
243+
// performance. We can only pass pointers down to `do_call` (can't pass
244+
// objects by value), so we do all the ownership tracking here manually
245+
// using a union.
247246
//
248247
// We go through a transition where:
249248
//
@@ -254,7 +253,7 @@ pub unsafe fn r#try<R, F: FnOnce() -> R>(f: F) -> Result<R, Box<dyn Any + Send>>
254253
// * If the closure successfully returns, we write the return value into the
255254
// data's return slot. Note that `ptr::write` is used as it's overwriting
256255
// uninitialized data.
257-
// * Finally, when we come back out of the `__rust_maybe_catch_panic` we're
256+
// * Finally, when we come back out of the `try` intrinsic we're
258257
// in one of two states:
259258
//
260259
// 1. The closure didn't panic, in which case the return value was
@@ -265,28 +264,30 @@ pub unsafe fn r#try<R, F: FnOnce() -> R>(f: F) -> Result<R, Box<dyn Any + Send>>
265264
//
266265
// Once we stack all that together we should have the "most efficient'
267266
// method of calling a catch panic whilst juggling ownership.
268-
let mut any_data = 0;
269-
let mut any_vtable = 0;
270267
let mut data = Data { f: ManuallyDrop::new(f) };
271268

272-
let r = __rust_maybe_catch_panic(
273-
do_call::<F, R>,
274-
&mut data as *mut _ as *mut u8,
275-
&mut any_data,
276-
&mut any_vtable,
277-
);
269+
let mut payload: MaybeUninit<TryPayload> = MaybeUninit::uninit();
278270

279-
return if r == 0 {
271+
let data_ptr = &mut data as *mut _ as *mut u8;
272+
let payload_ptr = payload.as_mut_ptr() as *mut _;
273+
return if intrinsics::r#try(do_call::<F, R>, data_ptr, payload_ptr) == 0 {
280274
debug_assert!(update_panic_count(0) == 0);
281275
Ok(ManuallyDrop::into_inner(data.r))
282276
} else {
277+
Err(cleanup(payload.assume_init()))
278+
};
279+
280+
// We consider unwinding to be rare, so mark this function as cold. However,
281+
// do not mark it no-inline -- that decision is best to leave to the
282+
// optimizer (in most cases this function is not inlined even as a normal,
283+
// non-cold function, though, as of the writing of this comment).
284+
#[cold]
285+
unsafe fn cleanup(payload: TryPayload) -> Box<dyn Any + Send + 'static> {
286+
let obj = Box::from_raw(__rust_panic_cleanup(payload));
283287
update_panic_count(-1);
284288
debug_assert!(update_panic_count(0) == 0);
285-
Err(mem::transmute(raw::TraitObject {
286-
data: any_data as *mut _,
287-
vtable: any_vtable as *mut _,
288-
}))
289-
};
289+
obj
290+
}
290291

291292
fn do_call<F: FnOnce() -> R, R>(data: *mut u8) {
292293
unsafe {

src/test/codegen/catch-unwind.rs

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// compile-flags: -O
2+
3+
#![crate_type = "lib"]
4+
5+
extern "C" {
6+
fn bar();
7+
}
8+
9+
// CHECK-LABEL: @foo
10+
#[no_mangle]
11+
pub unsafe fn foo() -> i32 {
12+
// CHECK: call void @bar
13+
// CHECK: ret i32 0
14+
std::panic::catch_unwind(|| {
15+
bar();
16+
0
17+
})
18+
.unwrap()
19+
}

src/test/codegen/try-panic-abort.rs

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// compile-flags: -C panic=abort -O
2+
3+
#![crate_type = "lib"]
4+
#![feature(unwind_attributes, core_intrinsics)]
5+
6+
extern "C" {
7+
#[unwind(allow)]
8+
fn bar(data: *mut u8);
9+
}
10+
11+
// CHECK-LABEL: @foo
12+
#[no_mangle]
13+
pub unsafe fn foo() -> i32 {
14+
// CHECK: call void @bar
15+
// CHECK: ret i32 0
16+
std::intrinsics::r#try(|x| bar(x), 0 as *mut u8, 0 as *mut u8)
17+
}

src/test/ui/no-landing-pads.rs

-23
This file was deleted.

src/tools/tidy/src/pal.rs

+2
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ const EXCEPTION_PATHS: &[&str] = &[
5959
"src/libstd/sys_common/mod.rs",
6060
"src/libstd/sys_common/net.rs",
6161
"src/libstd/sys_common/backtrace.rs",
62+
// panic_unwind shims
63+
"src/libstd/panicking.rs",
6264
"src/libterm", // Not sure how to make this crate portable, but test crate needs it.
6365
"src/libtest", // Probably should defer to unstable `std::sys` APIs.
6466
"src/libstd/sync/mpsc", // some tests are only run on non-emscripten

0 commit comments

Comments
 (0)