about summary refs log tree commit diff
path: root/src/libstd/sys/common
diff options
context:
space:
mode:
authorAlex Crichton <alex@alexcrichton.com>2015-10-23 18:18:44 -0700
committerAlex Crichton <alex@alexcrichton.com>2016-01-29 16:25:20 -0800
commit3e9589c0f43af69544b042f50b886005613540f2 (patch)
treeac718169258ed337a5164fe2943ad6b79a8c15df /src/libstd/sys/common
parentd1cace17af31ddb21aeb8a3d94cb3eda934047d9 (diff)
downloadrust-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/common')
-rw-r--r--src/libstd/sys/common/unwind/gcc.rs5
-rw-r--r--src/libstd/sys/common/unwind/mod.rs85
-rw-r--r--src/libstd/sys/common/unwind/seh.rs218
-rw-r--r--src/libstd/sys/common/unwind/seh64_gnu.rs5
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;