diff options
| author | bors <bors@rust-lang.org> | 2016-01-30 00:25:44 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2016-01-30 00:25:44 +0000 |
| commit | 303892ee156e5c31222b10786e661abb24dcf241 (patch) | |
| tree | 7e0f55d91bd180b8bc8ac8d700e99035da555fbf /src/libstd/sys | |
| parent | 074f49a350f22b6f33890cd105b2ebe2c790ee3c (diff) | |
| parent | 58f1b9c7fc93961fee8c94b6bdcbd5abfe84d5e0 (diff) | |
| download | rust-303892ee156e5c31222b10786e661abb24dcf241.tar.gz rust-303892ee156e5c31222b10786e661abb24dcf241.zip | |
Auto merge of #30448 - alexcrichton:llvmup, r=nikomatsakis
These commits perform a few high-level changes with the goal of enabling i686 MSVC unwinding: * LLVM is upgraded to pick up the new exception handling instructions and intrinsics for MSVC. This puts us somewhere along the 3.8 branch, but we should still be compatible with LLVM 3.7 for non-MSVC targets. * All unwinding for MSVC targets (both 32 and 64-bit) are implemented in terms of this new LLVM support. I would like to also extend this to Windows GNU targets to drop the runtime dependencies we have on MinGW, but I'd like to land this first. * Some tests were fixed up for i686 MSVC here and there where necessary. The full test suite should be passing now for that target. In terms of landing this I plan to have this go through first, then verify that i686 MSVC works, then I'll enable `make check` on the bots for that target instead of just `make` as-is today. Closes #25869
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; |
