diff options
| author | Alex Crichton <alex@alexcrichton.com> | 2015-10-23 18:18:44 -0700 |
|---|---|---|
| committer | Alex Crichton <alex@alexcrichton.com> | 2016-01-29 16:25:20 -0800 |
| commit | 3e9589c0f43af69544b042f50b886005613540f2 (patch) | |
| tree | ac718169258ed337a5164fe2943ad6b79a8c15df /src/libstd/sys | |
| parent | d1cace17af31ddb21aeb8a3d94cb3eda934047d9 (diff) | |
| download | rust-3e9589c0f43af69544b042f50b886005613540f2.tar.gz rust-3e9589c0f43af69544b042f50b886005613540f2.zip | |
trans: Reimplement unwinding on MSVC
This commit transitions the compiler to using the new exception handling instructions in LLVM for implementing unwinding for MSVC. This affects both 32 and 64-bit MSVC as they're both now using SEH-based strategies. In terms of standard library support, lots more details about how SEH unwinding is implemented can be found in the commits. In terms of trans, this change necessitated a few modifications: * Branches were added to detect when the old landingpad instruction is used or the new cleanuppad instruction is used to `trans::cleanup`. * The return value from `cleanuppad` is not stored in an `alloca` (because it cannot be). * Each block in trans now has an `Option<LandingPad>` instead of `is_lpad: bool` for indicating whether it's in a landing pad or not. The new exception handling intrinsics require that on MSVC each `call` inside of a landing pad is annotated with which landing pad that it's in. This change to the basic block means that whenever a `call` or `invoke` instruction is generated we know whether to annotate it as part of a cleanuppad or not. * Lots of modifications were made to the instruction builders to construct the new instructions as well as pass the tagging information for the call/invoke instructions. * The translation of the `try` intrinsics for MSVC has been overhauled to use the new `catchpad` instruction. The filter function is now also a rustc-generated function instead of a purely libstd-defined function. The libstd definition still exists, it just has a stable ABI across architectures and leaves some of the really weird implementation details to the compiler (e.g. the `localescape` and `localrecover` intrinsics).
Diffstat (limited to 'src/libstd/sys')
| -rw-r--r-- | src/libstd/sys/common/unwind/gcc.rs | 5 | ||||
| -rw-r--r-- | src/libstd/sys/common/unwind/mod.rs | 85 | ||||
| -rw-r--r-- | src/libstd/sys/common/unwind/seh.rs | 218 | ||||
| -rw-r--r-- | src/libstd/sys/common/unwind/seh64_gnu.rs | 5 |
4 files changed, 199 insertions, 114 deletions
diff --git a/src/libstd/sys/common/unwind/gcc.rs b/src/libstd/sys/common/unwind/gcc.rs index e9f2afbf55e..12cd07a4f4f 100644 --- a/src/libstd/sys/common/unwind/gcc.rs +++ b/src/libstd/sys/common/unwind/gcc.rs @@ -41,6 +41,11 @@ pub unsafe fn panic(data: Box<Any + Send + 'static>) -> ! { } } +#[cfg(not(stage0))] +pub fn payload() -> *mut u8 { + 0 as *mut u8 +} + pub unsafe fn cleanup(ptr: *mut u8) -> Box<Any + Send + 'static> { let my_ep = ptr as *mut Exception; let cause = (*my_ep).cause.take(); diff --git a/src/libstd/sys/common/unwind/mod.rs b/src/libstd/sys/common/unwind/mod.rs index 666b2ed72ad..d9641e63760 100644 --- a/src/libstd/sys/common/unwind/mod.rs +++ b/src/libstd/sys/common/unwind/mod.rs @@ -83,13 +83,13 @@ use sys_common::mutex::Mutex; // implementations. One goes through SEH on Windows and the other goes through // libgcc via the libunwind-like API. -// i686-pc-windows-msvc -#[cfg(all(windows, target_arch = "x86", target_env = "msvc"))] +// *-pc-windows-msvc +#[cfg(target_env = "msvc")] #[path = "seh.rs"] #[doc(hidden)] pub mod imp; -// x86_64-pc-windows-* -#[cfg(all(windows, target_arch = "x86_64"))] +// x86_64-pc-windows-gnu +#[cfg(all(windows, target_arch = "x86_64", target_env = "gnu"))] #[path = "seh64_gnu.rs"] #[doc(hidden)] pub mod imp; @@ -122,45 +122,54 @@ pub unsafe fn try<F: FnOnce()>(f: F) -> Result<(), Box<Any + Send>> { let mut f = Some(f); return inner_try(try_fn::<F>, &mut f as *mut _ as *mut u8); - // If an inner function were not used here, then this generic function `try` - // uses the native symbol `rust_try`, for which the code is statically - // linked into the standard library. This means that the DLL for the - // standard library must have `rust_try` as an exposed symbol that - // downstream crates can link against (because monomorphizations of `try` in - // downstream crates will have a reference to the `rust_try` symbol). - // - // On MSVC this requires the symbol `rust_try` to be tagged with - // `dllexport`, but it's easier to not have conditional `src/rt/rust_try.ll` - // files and instead just have this non-generic shim the compiler can take - // care of exposing correctly. - unsafe fn inner_try(f: fn(*mut u8), data: *mut u8) - -> Result<(), Box<Any + Send>> { - PANIC_COUNT.with(|s| { - let prev = s.get(); - s.set(0); - let ep = intrinsics::try(f, data); - s.set(prev); - if ep.is_null() { - Ok(()) - } else { - Err(imp::cleanup(ep)) - } - }) - } - fn try_fn<F: FnOnce()>(opt_closure: *mut u8) { let opt_closure = opt_closure as *mut Option<F>; unsafe { (*opt_closure).take().unwrap()(); } } +} - extern { - // Rust's try-catch - // When f(...) returns normally, the return value is null. - // When f(...) throws, the return value is a pointer to the caught - // exception object. - fn rust_try(f: extern fn(*mut u8), - data: *mut u8) -> *mut u8; - } +#[cfg(not(stage0))] +unsafe fn inner_try(f: fn(*mut u8), data: *mut u8) + -> Result<(), Box<Any + Send>> { + PANIC_COUNT.with(|s| { + let prev = s.get(); + s.set(0); + + // The "payload" here is a platform-specific region of memory which is + // used to transmit information about the exception being thrown from + // the point-of-throw back to this location. + // + // A pointer to this data is passed to the `try` intrinsic itself, + // allowing this function, the `try` intrinsic, imp::payload(), and + // imp::cleanup() to all work in concert to transmit this information. + // + // More information about what this pointer actually is can be found in + // each implementation as well as browsing the compiler source itself. + let mut payload = imp::payload(); + let r = intrinsics::try(f, data, &mut payload as *mut _ as *mut _); + s.set(prev); + if r == 0 { + Ok(()) + } else { + Err(imp::cleanup(payload)) + } + }) +} + +#[cfg(stage0)] +unsafe fn inner_try(f: fn(*mut u8), data: *mut u8) + -> Result<(), Box<Any + Send>> { + PANIC_COUNT.with(|s| { + let prev = s.get(); + s.set(0); + let ep = intrinsics::try(f, data); + s.set(prev); + if ep.is_null() { + Ok(()) + } else { + Err(imp::cleanup(ep)) + } + }) } /// Determines whether the current thread is unwinding because of panic. diff --git a/src/libstd/sys/common/unwind/seh.rs b/src/libstd/sys/common/unwind/seh.rs index 7296194efda..f8d3a92b3b6 100644 --- a/src/libstd/sys/common/unwind/seh.rs +++ b/src/libstd/sys/common/unwind/seh.rs @@ -8,109 +8,175 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! Win64 SEH (see http://msdn.microsoft.com/en-us/library/1eyas8tf.aspx) +//! Windows SEH //! //! On Windows (currently only on MSVC), the default exception handling //! mechanism is Structured Exception Handling (SEH). This is quite different //! than Dwarf-based exception handling (e.g. what other unix platforms use) in //! terms of compiler internals, so LLVM is required to have a good deal of -//! extra support for SEH. Currently this support is somewhat lacking, so what's -//! here is the bare bones of SEH support. +//! extra support for SEH. //! //! In a nutshell, what happens here is: //! //! 1. The `panic` function calls the standard Windows function `RaiseException` //! with a Rust-specific code, triggering the unwinding process. -//! 2. All landing pads generated by the compiler (just "cleanup" landing pads) -//! use the personality function `__C_specific_handler`, a function in the -//! CRT, and the unwinding code in Windows will use this personality function -//! to execute all cleanup code on the stack. -//! 3. Eventually the "catch" code in `rust_try` (located in -//! src/rt/rust_try_msvc_64.ll) is executed, which will ensure that the -//! exception being caught is indeed a Rust exception, returning control back -//! into Rust. +//! 2. All landing pads generated by the compiler use the personality function +//! `__C_specific_handler` on 64-bit and `__except_handler3` on 32-bit, +//! functions in the CRT, and the unwinding code in Windows will use this +//! personality function to execute all cleanup code on the stack. +//! 3. All compiler-generated calls to `invoke` have a landing pad set as a +//! `cleanuppad` LLVM instruction, which indicates the start of the cleanup +//! routine. The personality (in step 2, defined in the CRT) is responsible +//! for running the cleanup routines. +//! 4. Eventually the "catch" code in the `try` intrinsic (generated by the +//! compiler) is executed, which will ensure that the exception being caught +//! is indeed a Rust exception, indicating that control should come back to +//! Rust. This is done via a `catchswitch` plus a `catchpad` instruction in +//! LLVM IR terms, finally returning normal control to the program with a +//! `catchret` instruction. The `try` intrinsic uses a filter function to +//! detect what kind of exception is being thrown, and this detection is +//! implemented as the msvc_try_filter language item below. //! //! Some specific differences from the gcc-based exception handling are: //! //! * Rust has no custom personality function, it is instead *always* -//! __C_specific_handler, so the filtering is done in a C++-like manner -//! instead of in the personality function itself. Note that the specific -//! syntax for this (found in the rust_try_msvc_64.ll) is taken from an LLVM -//! test case for SEH. +//! __C_specific_handler or __except_handler3, so the filtering is done in a +//! C++-like manner instead of in the personality function itself. Note that +//! the precise codegen for this was lifted from an LLVM test case for SEH +//! (this is the `__rust_try_filter` function below). //! * We've got some data to transmit across the unwinding boundary, -//! specifically a `Box<Any + Send + 'static>`. In Dwarf-based unwinding this -//! data is part of the payload of the exception, but I have not currently -//! figured out how to do this with LLVM's bindings. Judging by some comments -//! in the LLVM test cases this may not even be possible currently with LLVM, -//! so this is just abandoned entirely. Instead the data is stored in a -//! thread-local in `panic` and retrieved during `cleanup`. +//! specifically a `Box<Any + Send + 'static>`. Like with Dwarf exceptions +//! these two pointers are stored as a payload in the exception itself. On +//! MSVC, however, there's no need for an extra allocation because the call +//! stack is preserved while filter functions are being executed. This means +//! that the pointers are passed directly to `RaiseException` which are then +//! recovered in the filter function to be written to the stack frame of the +//! `try` intrinsic. //! -//! So given all that, the bindings here are pretty small, +//! [win64]: http://msdn.microsoft.com/en-us/library/1eyas8tf.aspx +//! [llvm]: http://llvm.org/docs/ExceptionHandling.html#background-on-windows-exceptions -#![allow(bad_style)] +use sys::c; -use prelude::v1::*; +// A code which indicates panics that originate from Rust. Note that some of the +// upper bits are used by the system so we just set them to 0 and ignore them. +// 0x 0 R S T +const RUST_PANIC: c::DWORD = 0x00525354; -use any::Any; -use ptr; -use sys_common::thread_local::StaticKey; -use sys::c; +pub use self::imp::*; + +#[cfg(stage0)] +mod imp { + use prelude::v1::*; + use any::Any; -// 0x R U S T -const RUST_PANIC: c::DWORD = 0x52555354; -static PANIC_DATA: StaticKey = StaticKey::new(None); - -pub unsafe fn panic(data: Box<Any + Send + 'static>) -> ! { - // See module docs above for an explanation of why `data` is stored in a - // thread local instead of being passed as an argument to the - // `RaiseException` function (which can in theory carry along arbitrary - // data). - let exception = Box::new(data); - rtassert!(PANIC_DATA.get().is_null()); - PANIC_DATA.set(Box::into_raw(exception) as *mut u8); - - c::RaiseException(RUST_PANIC, 0, 0, ptr::null()); - rtabort!("could not unwind stack"); + pub unsafe fn panic(_data: Box<Any + Send + 'static>) -> ! { + rtabort!("cannot unwind SEH in stage0") + } + + pub unsafe fn cleanup(_ptr: *mut u8) -> Box<Any + Send + 'static> { + rtabort!("can't cleanup SEH in stage0") + } + + #[lang = "msvc_try_filter"] + #[linkage = "external"] + unsafe extern fn __rust_try_filter() -> i32 { + 0 + } + + #[lang = "eh_unwind_resume"] + #[unwind] + unsafe extern fn rust_eh_unwind_resume(_ptr: *mut u8) -> ! { + rtabort!("can't resume unwind SEH in stage0") + } + #[lang = "eh_personality_catch"] + unsafe extern fn rust_eh_personality_catch() {} } -pub unsafe fn cleanup(ptr: *mut u8) -> Box<Any + Send + 'static> { - // The `ptr` here actually corresponds to the code of the exception, and our - // real data is stored in our thread local. - rtassert!(ptr as c::DWORD == RUST_PANIC); +#[cfg(not(stage0))] +mod imp { + use prelude::v1::*; + + use any::Any; + use mem; + use raw; + use super::RUST_PANIC; + use sys::c; - let data = PANIC_DATA.get() as *mut Box<Any + Send + 'static>; - PANIC_DATA.set(ptr::null_mut()); - rtassert!(!data.is_null()); + pub unsafe fn panic(data: Box<Any + Send + 'static>) -> ! { + // As mentioned above, the call stack here is preserved while the filter + // functions are running, so it's ok to pass stack-local arrays into + // `RaiseException`. + // + // The two pointers of the `data` trait object are written to the stack, + // passed to `RaiseException`, and they're later extracted by the filter + // function below in the "custom exception information" section of the + // `EXCEPTION_RECORD` type. + let ptrs = mem::transmute::<_, raw::TraitObject>(data); + let ptrs = [ptrs.data, ptrs.vtable]; + c::RaiseException(RUST_PANIC, 0, 2, ptrs.as_ptr() as *mut _); + rtabort!("could not unwind stack"); + } + + pub fn payload() -> [usize; 2] { + [0; 2] + } - *Box::from_raw(data) + pub unsafe fn cleanup(payload: [usize; 2]) -> Box<Any + Send + 'static> { + mem::transmute(raw::TraitObject { + data: payload[0] as *mut _, + vtable: payload[1] as *mut _, + }) + } + + // This is quite a special function, and it's not literally passed in as the + // filter function for the `catchpad` of the `try` intrinsic. The compiler + // actually generates its own filter function wrapper which will delegate to + // this for the actual execution logic for whether the exception should be + // caught. The reasons for this are: + // + // * Each architecture has a slightly different ABI for the filter function + // here. For example on x86 there are no arguments but on x86_64 there are + // two. + // * This function needs access to the stack frame of the `try` intrinsic + // which is using this filter as a catch pad. This is because the payload + // of this exception, `Box<Any>`, needs to be transmitted to that + // location. + // + // Both of these differences end up using a ton of weird llvm-specific + // intrinsics, so it's actually pretty difficult to express the entire + // filter function in Rust itself. As a compromise, the compiler takes care + // of all the weird LLVM-specific and platform-specific stuff, getting to + // the point where this function makes the actual decision about what to + // catch given two parameters. + // + // The first parameter is `*mut EXCEPTION_POINTERS` which is some contextual + // information about the exception being filtered, and the second pointer is + // `*mut *mut [usize; 2]` (the payload here). This value points directly + // into the stack frame of the `try` intrinsic itself, and we use it to copy + // information from the exception onto the stack. + #[lang = "msvc_try_filter"] + #[cfg(not(test))] + unsafe extern fn __rust_try_filter(eh_ptrs: *mut u8, + payload: *mut u8) -> i32 { + let eh_ptrs = eh_ptrs as *mut c::EXCEPTION_POINTERS; + let payload = payload as *mut *mut [usize; 2]; + let record = &*(*eh_ptrs).ExceptionRecord; + if record.ExceptionCode != RUST_PANIC { + return 0 + } + (**payload)[0] = record.ExceptionInformation[0] as usize; + (**payload)[1] = record.ExceptionInformation[1] as usize; + return 1 + } } -// This is required by the compiler to exist (e.g. it's a lang item), but it's -// never actually called by the compiler because __C_specific_handler is the -// personality function that is always used. Hence this is just an aborting -// stub. +// This is required by the compiler to exist (e.g. it's a lang item), but +// it's never actually called by the compiler because __C_specific_handler +// or _except_handler3 is the personality function that is always used. +// Hence this is just an aborting stub. #[lang = "eh_personality"] +#[cfg(not(test))] fn rust_eh_personality() { unsafe { ::intrinsics::abort() } } - -// This is a function referenced from `rust_try_msvc_64.ll` which is used to -// filter the exceptions being caught by that function. -// -// In theory local variables can be accessed through the `rbp` parameter of this -// function, but a comment in an LLVM test case indicates that this is not -// implemented in LLVM, so this is just an idempotent function which doesn't -// ferry along any other information. -// -// This function just takes a look at the current EXCEPTION_RECORD being thrown -// to ensure that it's code is RUST_PANIC, which was set by the call to -// `RaiseException` above in the `panic` function. -#[lang = "msvc_try_filter"] -#[linkage = "external"] -#[allow(private_no_mangle_fns)] -extern fn __rust_try_filter(eh_ptrs: *mut c::EXCEPTION_POINTERS, - _rbp: *mut u8) -> i32 { - unsafe { - ((*(*eh_ptrs).ExceptionRecord).ExceptionCode == RUST_PANIC) as i32 - } -} diff --git a/src/libstd/sys/common/unwind/seh64_gnu.rs b/src/libstd/sys/common/unwind/seh64_gnu.rs index 26c2cee9222..8afef081673 100644 --- a/src/libstd/sys/common/unwind/seh64_gnu.rs +++ b/src/libstd/sys/common/unwind/seh64_gnu.rs @@ -50,6 +50,11 @@ pub unsafe fn panic(data: Box<Any + Send + 'static>) -> ! { rtabort!("could not unwind stack"); } +#[cfg(not(stage0))] +pub fn payload() -> *mut u8 { + 0 as *mut u8 +} + pub unsafe fn cleanup(ptr: *mut u8) -> Box<Any + Send + 'static> { let panic_ctx = Box::from_raw(ptr as *mut PanicData); return panic_ctx.data; |
