Skip to content

Commit b9e6323

Browse files
committed
Auto merge of #67502 - Mark-Simulacrum:opt-catch, r=<try>
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 9ed29b6 + b82af92 commit b9e6323

File tree

18 files changed

+174
-155
lines changed

18 files changed

+174
-155
lines changed

Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -2309,6 +2309,7 @@ dependencies = [
23092309
name = "panic_abort"
23102310
version = "0.0.0"
23112311
dependencies = [
2312+
"cfg-if",
23122313
"compiler_builtins",
23132314
"core",
23142315
"libc",

src/ci/azure-pipelines/try.yml

+28-33
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,17 @@ variables:
66
- group: prod-credentials
77

88
jobs:
9-
- job: Linux
10-
timeoutInMinutes: 600
11-
pool:
12-
vmImage: ubuntu-16.04
13-
steps:
14-
- template: steps/run.yml
15-
strategy:
16-
matrix:
17-
dist-x86_64-linux: {}
18-
dist-x86_64-linux-alt:
19-
IMAGE: dist-x86_64-linux
9+
# - job: Linux
10+
# timeoutInMinutes: 600
11+
# pool:
12+
# vmImage: ubuntu-16.04
13+
# steps:
14+
# - template: steps/run.yml
15+
# strategy:
16+
# matrix:
17+
# dist-x86_64-linux: {}
18+
# dist-x86_64-linux-alt:
19+
# IMAGE: dist-x86_64-linux
2020

2121
# The macOS and Windows builds here are currently disabled due to them not being
2222
# overly necessary on `try` builds. We also don't actually have anything that
@@ -49,25 +49,20 @@ jobs:
4949
# NO_LLVM_ASSERTIONS: 1
5050
# NO_DEBUG_ASSERTIONS: 1
5151
#
52-
# - job: Windows
53-
# timeoutInMinutes: 600
54-
# pool:
55-
# vmImage: 'vs2017-win2016'
56-
# steps:
57-
# - template: steps/run.yml
58-
# strategy:
59-
# matrix:
60-
# dist-x86_64-msvc:
61-
# RUST_CONFIGURE_ARGS: >
62-
# --build=x86_64-pc-windows-msvc
63-
# --target=x86_64-pc-windows-msvc,aarch64-pc-windows-msvc
64-
# --enable-full-tools
65-
# --enable-profiler
66-
# SCRIPT: python x.py dist
67-
# DIST_REQUIRE_ALL_TOOLS: 1
68-
# DEPLOY: 1
69-
#
70-
# dist-x86_64-msvc-alt:
71-
# RUST_CONFIGURE_ARGS: --build=x86_64-pc-windows-msvc --enable-extended --enable-profiler
72-
# SCRIPT: python x.py dist
73-
# DEPLOY_ALT: 1
52+
- job: Windows
53+
timeoutInMinutes: 600
54+
pool:
55+
vmImage: 'vs2017-win2016'
56+
steps:
57+
- template: steps/run.yml
58+
strategy:
59+
matrix:
60+
i686-mingw-2:
61+
RUST_CONFIGURE_ARGS: --build=i686-pc-windows-gnu
62+
SCRIPT: make ci-mingw-subset-2
63+
CUSTOM_MINGW: 1
64+
dist-i686-mingw:
65+
RUST_CONFIGURE_ARGS: --build=i686-pc-windows-gnu --enable-debug-assertions --enable-optimize --debuginfo-level=1
66+
SCRIPT: python x.py dist
67+
CUSTOM_MINGW: 1
68+
N_DIST_REQUIRE_ALL_TOOLS: 1

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

-1
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,6 @@ the source code.
248248
- `eh_personality`: `libpanic_unwind/gcc.rs` (GNU)
249249
- `eh_personality`: `libpanic_unwind/seh.rs` (SEH)
250250
- `eh_unwind_resume`: `libpanic_unwind/gcc.rs` (GCC)
251-
- `eh_catch_typeinfo`: `libpanic_unwind/seh.rs` (SEH)
252251
- `eh_catch_typeinfo`: `libpanic_unwind/emcc.rs` (EMCC)
253252
- `panic`: `libcore/panicking.rs`
254253
- `panic_bounds_check`: `libcore/panicking.rs`

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
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

src/libpanic_unwind/gcc.rs

+1-9
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};
@@ -83,10 +82,6 @@ pub unsafe fn panic(data: Box<dyn Any + Send>) -> u32 {
8382
}
8483
}
8584

86-
pub fn payload() -> *mut u8 {
87-
ptr::null_mut()
88-
}
89-
9085
pub unsafe fn cleanup(ptr: *mut u8) -> Box<dyn Any + Send> {
9186
let exception = Box::from_raw(ptr as *mut Exception);
9287
exception.cause
@@ -336,10 +331,7 @@ unsafe fn find_eh_action(
336331
target_env = "gnu"
337332
))]
338333
#[lang = "eh_unwind_resume"]
339-
#[unwind(allowed)]
340-
unsafe extern "C" fn rust_eh_unwind_resume(panic_ctx: *mut u8) -> ! {
341-
uw::_Unwind_Resume(panic_ctx as *mut uw::_Unwind_Exception);
342-
}
334+
use uw::_Unwind_Resume as rust_eh_unwind_resume;
343335

344336
// Frame unwind info registration
345337
//

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,20 +22,21 @@
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

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

65+
include!("payload.rs");
66+
6467
extern "C" {
6568
/// Handler in libstd called when a panic object is dropped outside of
6669
/// `catch_unwind`.
@@ -69,28 +72,11 @@ extern "C" {
6972

7073
mod dwarf;
7174

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.
7875
#[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-
}
76+
pub unsafe extern "C" fn __rust_panic_cleanup(
77+
payload: TryPayload,
78+
) -> *mut (dyn Any + Send + 'static) {
79+
Box::into_raw(imp::cleanup(payload))
9480
}
9581

9682
// 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

+8-9
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,9 @@ pub struct _TypeDescriptor {
167167

168168
// Note that we intentionally ignore name mangling rules here: we don't want C++
169169
// to be able to catch Rust panics by simply declaring a `struct rust_panic`.
170+
//
171+
// When modifying, make sure that the type name string exactly matches
172+
// the one used in src/librustc_codegen_llvm/intrinsic.rs.
170173
const TYPE_NAME: [u8; 11] = *b"rust_panic\0";
171174

172175
static mut THROW_INFO: _ThrowInfo = _ThrowInfo {
@@ -199,12 +202,12 @@ extern "C" {
199202
static TYPE_INFO_VTABLE: *const u8;
200203
}
201204

202-
// We use #[lang = "eh_catch_typeinfo"] here as this is the type descriptor which
203-
// we'll use in LLVM's `catchpad` instruction which ends up also being passed as
204-
// an argument to the C++ personality function.
205+
// This type descriptor is only used when throwing an exception. The catch part
206+
// is handled by the try intrinsic, which generates its own TypeDescriptor.
205207
//
206-
// Again, I'm not entirely sure what this is describing, it just seems to work.
207-
#[cfg_attr(not(test), lang = "eh_catch_typeinfo")]
208+
// This is fine since the MSVC runtime uses string comparison on the type name
209+
// to match TypeDescriptors rather than pointer equality.
210+
#[cfg_attr(bootstrap, lang = "eh_catch_typeinfo")]
208211
static mut TYPE_DESCRIPTOR: _TypeDescriptor = _TypeDescriptor {
209212
pVFTable: unsafe { &TYPE_INFO_VTABLE } as *const _ as *const _,
210213
spare: core::ptr::null_mut(),
@@ -315,10 +318,6 @@ pub unsafe fn panic(data: Box<dyn Any + Send>) -> u32 {
315318
_CxxThrowException(throw_ptr, &mut THROW_INFO as *mut _ as *mut _);
316319
}
317320

318-
pub fn payload() -> [u64; 2] {
319-
[0; 2]
320-
}
321-
322321
pub unsafe fn cleanup(payload: [u64; 2]) -> Box<dyn Any + Send> {
323322
mem::transmute(raw::TraitObject { data: payload[0] as *mut _, vtable: payload[1] as *mut _ })
324323
}

src/librustc_codegen_llvm/intrinsic.rs

+31-7
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 {
@@ -952,19 +954,39 @@ fn codegen_msvc_try(
952954
let cs = catchswitch.catch_switch(None, None, 1);
953955
catchswitch.add_handler(cs, catchpad.llbb());
954956

957+
// We can't use the TypeDescriptor defined in libpanic_unwind because it
958+
// might be in another DLL and the SEH encoding only supports specifying
959+
// a TypeDescriptor from the current module.
960+
//
961+
// However this isn't an issue since the MSVC runtime uses string
962+
// comparison on the type name to match TypeDescriptors rather than
963+
// pointer equality.
964+
//
965+
// So instead we generate a new TypeDescriptor in each module that uses
966+
// `try` and let the linker merge duplicate definitions in the same
967+
// module.
968+
//
969+
// When modifying, make sure that the type_name string exactly matches
970+
// the one used in src/libpanic_unwind/seh.rs.
971+
let type_info_vtable = bx.declare_global("??_7type_info@@6B@", bx.type_i8p());
972+
let type_name = bx.const_bytes(b"rust_panic\0");
973+
let type_info =
974+
bx.const_struct(&[type_info_vtable, bx.const_null(bx.type_i8p()), type_name], false);
975+
let tydesc = bx.declare_global("__rust_panic_type_info", bx.val_ty(type_info));
976+
unsafe {
977+
llvm::LLVMRustSetLinkage(tydesc, llvm::Linkage::LinkOnceODRLinkage);
978+
llvm::SetUniqueComdat(bx.llmod, tydesc);
979+
llvm::LLVMSetInitializer(tydesc, type_info);
980+
}
981+
955982
// The flag value of 8 indicates that we are catching the exception by
956983
// reference instead of by value. We can't use catch by value because
957984
// that requires copying the exception object, which we don't support
958985
// since our exception object effectively contains a Box.
959986
//
960987
// Source: MicrosoftCXXABI::getAddrOfCXXCatchHandlerType in clang
961988
let flags = bx.const_i32(8);
962-
let tydesc = match bx.tcx().lang_items().eh_catch_typeinfo() {
963-
Some(did) => bx.get_static(did),
964-
None => bug!("eh_catch_typeinfo not defined, but needed for SEH unwinding"),
965-
};
966989
let funclet = catchpad.catch_pad(cs, &[tydesc, flags, slot]);
967-
968990
let i64_align = bx.tcx().data_layout.i64_align.abi;
969991
let payload_ptr = catchpad.load(slot, ptr_align);
970992
let payload = catchpad.load(payload_ptr, i64_align);
@@ -1084,6 +1106,8 @@ fn gen_fn<'ll, 'tcx>(
10841106
));
10851107
let fn_abi = FnAbi::of_fn_ptr(cx, rust_fn_sig, &[]);
10861108
let llfn = cx.declare_fn(name, &fn_abi);
1109+
cx.set_frame_pointer_elimination(llfn);
1110+
cx.apply_target_cpu_attr(llfn);
10871111
// FIXME(eddyb) find a nicer way to do this.
10881112
unsafe { llvm::LLVMRustSetLinkage(llfn, llvm::Linkage::InternalLinkage) };
10891113
let bx = Builder::new_block(cx, llfn, "entry-block");

0 commit comments

Comments
 (0)