Skip to content

Commit

Permalink
Auto merge of #67502 - Mark-Simulacrum:opt-catch, r=<try>
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
bors committed Jan 30, 2020
2 parents c4071d0 + b01f755 commit e23f5a7
Show file tree
Hide file tree
Showing 21 changed files with 190 additions and 165 deletions.
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2309,6 +2309,7 @@ dependencies = [
name = "panic_abort"
version = "0.0.0"
dependencies = [
"cfg-if",
"compiler_builtins",
"core",
"libc",
Expand Down
61 changes: 28 additions & 33 deletions src/ci/azure-pipelines/try.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ variables:
- group: prod-credentials

jobs:
- job: Linux
timeoutInMinutes: 600
pool:
vmImage: ubuntu-16.04
steps:
- template: steps/run.yml
strategy:
matrix:
dist-x86_64-linux: {}
dist-x86_64-linux-alt:
IMAGE: dist-x86_64-linux
# - job: Linux
# timeoutInMinutes: 600
# pool:
# vmImage: ubuntu-16.04
# steps:
# - template: steps/run.yml
# strategy:
# matrix:
# dist-x86_64-linux: {}
# dist-x86_64-linux-alt:
# IMAGE: dist-x86_64-linux

# The macOS and Windows builds here are currently disabled due to them not being
# overly necessary on `try` builds. We also don't actually have anything that
Expand Down Expand Up @@ -49,25 +49,20 @@ jobs:
# NO_LLVM_ASSERTIONS: 1
# NO_DEBUG_ASSERTIONS: 1
#
# - job: Windows
# timeoutInMinutes: 600
# pool:
# vmImage: 'vs2017-win2016'
# steps:
# - template: steps/run.yml
# strategy:
# matrix:
# dist-x86_64-msvc:
# RUST_CONFIGURE_ARGS: >
# --build=x86_64-pc-windows-msvc
# --target=x86_64-pc-windows-msvc,aarch64-pc-windows-msvc
# --enable-full-tools
# --enable-profiler
# SCRIPT: python x.py dist
# DIST_REQUIRE_ALL_TOOLS: 1
# DEPLOY: 1
#
# dist-x86_64-msvc-alt:
# RUST_CONFIGURE_ARGS: --build=x86_64-pc-windows-msvc --enable-extended --enable-profiler
# SCRIPT: python x.py dist
# DEPLOY_ALT: 1
- job: Windows
timeoutInMinutes: 600
pool:
vmImage: 'vs2017-win2016'
steps:
- template: steps/run.yml
strategy:
matrix:
i686-mingw-2:
RUST_CONFIGURE_ARGS: --build=i686-pc-windows-gnu
SCRIPT: make ci-mingw-subset-2
CUSTOM_MINGW: 1
dist-i686-mingw:
RUST_CONFIGURE_ARGS: --build=i686-pc-windows-gnu --enable-debug-assertions --enable-optimize --debuginfo-level=1
SCRIPT: python x.py dist
CUSTOM_MINGW: 1
N_DIST_REQUIRE_ALL_TOOLS: 1
1 change: 0 additions & 1 deletion src/doc/unstable-book/src/language-features/lang-items.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,6 @@ the source code.
- `eh_personality`: `libpanic_unwind/gcc.rs` (GNU)
- `eh_personality`: `libpanic_unwind/seh.rs` (SEH)
- `eh_unwind_resume`: `libpanic_unwind/gcc.rs` (GCC)
- `eh_catch_typeinfo`: `libpanic_unwind/seh.rs` (SEH)
- `eh_catch_typeinfo`: `libpanic_unwind/emcc.rs` (EMCC)
- `panic`: `libcore/panicking.rs`
- `panic_bounds_check`: `libcore/panicking.rs`
Expand Down
1 change: 1 addition & 0 deletions src/libpanic_abort/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ doc = false
core = { path = "../libcore" }
libc = { version = "0.2", default-features = false }
compiler_builtins = "0.1.0"
cfg-if = "0.1.8"
17 changes: 7 additions & 10 deletions src/libpanic_abort/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,14 @@
#![feature(staged_api)]
#![feature(rustc_attrs)]

// Rust's "try" function, but if we're aborting on panics we just call the
// function as there's nothing else we need to do here.
use core::any::Any;

// We need the definition of TryPayload for __rust_panic_cleanup.
include!("../libpanic_unwind/payload.rs");

#[rustc_std_internal_symbol]
pub unsafe extern "C" fn __rust_maybe_catch_panic(
f: fn(*mut u8),
data: *mut u8,
_data_ptr: *mut usize,
_vtable_ptr: *mut usize,
) -> u32 {
f(data);
0
pub unsafe extern "C" fn __rust_panic_cleanup(_: TryPayload) -> *mut (dyn Any + Send + 'static) {
unreachable!()
}

// "Leak" the payload and shim to the relevant abort on the platform in
Expand Down
4 changes: 0 additions & 4 deletions src/libpanic_unwind/dummy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ use alloc::boxed::Box;
use core::any::Any;
use core::intrinsics;

pub fn payload() -> *mut u8 {
core::ptr::null_mut()
}

pub unsafe fn cleanup(_ptr: *mut u8) -> Box<dyn Any + Send> {
intrinsics::abort()
}
Expand Down
4 changes: 0 additions & 4 deletions src/libpanic_unwind/emcc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,6 @@ static EXCEPTION_TYPE_INFO: TypeInfo = TypeInfo {
name: b"rust_panic\0".as_ptr(),
};

pub fn payload() -> *mut u8 {
ptr::null_mut()
}

struct Exception {
// This needs to be an Option because the object's lifetime follows C++
// semantics: when catch_unwind moves the Box out of the exception it must
Expand Down
22 changes: 13 additions & 9 deletions src/libpanic_unwind/gcc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@

use alloc::boxed::Box;
use core::any::Any;
use core::ptr;

use crate::dwarf::eh::{self, EHAction, EHContext};
use libc::{c_int, uintptr_t};
Expand Down Expand Up @@ -83,10 +82,6 @@ pub unsafe fn panic(data: Box<dyn Any + Send>) -> u32 {
}
}

pub fn payload() -> *mut u8 {
ptr::null_mut()
}

pub unsafe fn cleanup(ptr: *mut u8) -> Box<dyn Any + Send> {
let exception = Box::from_raw(ptr as *mut Exception);
exception.cause
Expand Down Expand Up @@ -329,16 +324,25 @@ unsafe fn find_eh_action(
eh::find_eh_action(lsda, &eh_context, foreign_exception)
}

// See docs in the `unwind` module.
#[cfg(all(
target_os = "windows",
any(target_arch = "x86", target_arch = "x86_64"),
target_env = "gnu"
))]
#[cfg_attr(not(bootstrap), lang = "eh_unwind_resume")]
#[used]
pub static RESUME: unsafe extern "C" fn(*mut uw::_Unwind_Exception) -> ! =
uw::_Unwind_Resume as unsafe extern "C" fn(_) -> !;

#[cfg(all(
target_os = "windows",
any(target_arch = "x86", target_arch = "x86_64"),
target_env = "gnu"
))]
#[lang = "eh_unwind_resume"]
#[unwind(allowed)]
unsafe extern "C" fn rust_eh_unwind_resume(panic_ctx: *mut u8) -> ! {
uw::_Unwind_Resume(panic_ctx as *mut uw::_Unwind_Exception);
#[cfg(bootstrap)]
pub unsafe extern "C" fn rust_eh_unwind_resume(p: *mut u8) -> ! {
uw::_Unwind_Resume(p as *mut uw::_Unwind_Exception)
}

// Frame unwind info registration
Expand Down
4 changes: 0 additions & 4 deletions src/libpanic_unwind/hermit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ use alloc::boxed::Box;
use core::any::Any;
use core::ptr;

pub fn payload() -> *mut u8 {
ptr::null_mut()
}

pub unsafe fn cleanup(_ptr: *mut u8) -> Box<dyn Any + Send> {
extern "C" {
pub fn __rust_abort() -> !;
Expand Down
37 changes: 12 additions & 25 deletions src/libpanic_unwind/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,22 @@
#![feature(libc)]
#![feature(nll)]
#![feature(panic_unwind)]
#![feature(raw)]
#![feature(staged_api)]
#![feature(std_internals)]
#![feature(unwind_attributes)]
#![feature(abi_thiscall)]
#![feature(rustc_attrs)]
#![feature(raw)]
#![panic_runtime]
#![feature(panic_runtime)]
#![allow(dead_code)]

use alloc::boxed::Box;
use core::intrinsics;
use core::mem;
use core::any::Any;
use core::panic::BoxMeUp;
use core::raw;

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

include!("payload.rs");

extern "C" {
/// Handler in libstd called when a panic object is dropped outside of
/// `catch_unwind`.
Expand All @@ -69,28 +73,11 @@ extern "C" {

mod dwarf;

// Entry point for catching an exception, implemented using the `try` intrinsic
// in the compiler.
//
// The interaction between the `payload` function and the compiler is pretty
// hairy and tightly coupled, for more information see the compiler's
// implementation of this.
#[no_mangle]
pub unsafe extern "C" fn __rust_maybe_catch_panic(
f: fn(*mut u8),
data: *mut u8,
data_ptr: *mut usize,
vtable_ptr: *mut usize,
) -> u32 {
let mut payload = imp::payload();
if intrinsics::r#try(f, data, &mut payload as *mut _ as *mut _) == 0 {
0
} else {
let obj = mem::transmute::<_, raw::TraitObject>(imp::cleanup(payload));
*data_ptr = obj.data as usize;
*vtable_ptr = obj.vtable as usize;
1
}
pub unsafe extern "C" fn __rust_panic_cleanup(
payload: TryPayload,
) -> *mut (dyn Any + Send + 'static) {
Box::into_raw(imp::cleanup(payload))
}

// Entry point for raising an exception, just delegates to the platform-specific
Expand Down
21 changes: 21 additions & 0 deletions src/libpanic_unwind/payload.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Type definition for the payload argument of the try intrinsic.
//
// This must be kept in sync with the implementations of the try intrinsic.
//
// This file is included by both panic runtimes and libstd. It is part of the
// panic runtime ABI.
cfg_if::cfg_if! {
if #[cfg(target_os = "emscripten")] {
type TryPayload = *mut u8;
} else if #[cfg(target_arch = "wasm32")] {
type TryPayload = *mut u8;
} else if #[cfg(target_os = "hermit")] {
type TryPayload = *mut u8;
} else if #[cfg(all(target_env = "msvc", target_arch = "aarch64"))] {
type TryPayload = *mut u8;
} else if #[cfg(target_env = "msvc")] {
type TryPayload = [u64; 2];
} else {
type TryPayload = *mut u8;
}
}
17 changes: 8 additions & 9 deletions src/libpanic_unwind/seh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ pub struct _TypeDescriptor {

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

static mut THROW_INFO: _ThrowInfo = _ThrowInfo {
Expand Down Expand Up @@ -199,12 +202,12 @@ extern "C" {
static TYPE_INFO_VTABLE: *const u8;
}

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

pub fn payload() -> [u64; 2] {
[0; 2]
}

pub unsafe fn cleanup(payload: [u64; 2]) -> Box<dyn Any + Send> {
mem::transmute(raw::TraitObject { data: payload[0] as *mut _, vtable: payload[1] as *mut _ })
}
Expand Down
2 changes: 1 addition & 1 deletion src/librustc/middle/lang_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ language_item_table! {
StartFnLangItem, "start", start_fn, Target::Fn;

EhPersonalityLangItem, "eh_personality", eh_personality, Target::Fn;
EhUnwindResumeLangItem, "eh_unwind_resume", eh_unwind_resume, Target::Fn;
EhUnwindResumeLangItem, "eh_unwind_resume", eh_unwind_resume, Target::Static;
EhCatchTypeinfoLangItem, "eh_catch_typeinfo", eh_catch_typeinfo, Target::Static;

OwnedBoxLangItem, "owned_box", owned_box, Target::Struct;
Expand Down
10 changes: 1 addition & 9 deletions src/librustc_codegen_llvm/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -407,15 +407,7 @@ impl MiscMethods<'tcx> for CodegenCx<'ll, 'tcx> {
let tcx = self.tcx;
assert!(self.sess().target.target.options.custom_unwind_resume);
if let Some(def_id) = tcx.lang_items().eh_unwind_resume() {
let llfn = self.get_fn_addr(
ty::Instance::resolve(
tcx,
ty::ParamEnv::reveal_all(),
def_id,
tcx.intern_substs(&[]),
)
.unwrap(),
);
let llfn = self.get_static(def_id);
unwresume.set(Some(llfn));
return llfn;
}
Expand Down
Loading

0 comments on commit e23f5a7

Please sign in to comment.