NATIVE_DEPS_miniz_$(1) = miniz.c
NATIVE_DEPS_rust_builtin_$(1) := rust_builtin.c \
rust_android_dummy.c
-NATIVE_DEPS_rustrt_native_$(1) := \
- rust_try.ll \
- arch/$$(HOST_$(1))/record_sp.S
+NATIVE_DEPS_rustrt_native_$(1) := arch/$$(HOST_$(1))/record_sp.S
+ifeq ($$(findstring msvc,$(1)),msvc)
+NATIVE_DEPS_rustrt_native_$(1) += rust_try_msvc_64.ll
+else
+NATIVE_DEPS_rustrt_native_$(1) += rust_try.ll
+endif
NATIVE_DEPS_rust_test_helpers_$(1) := rust_test_helpers.c
NATIVE_DEPS_morestack_$(1) := arch/$$(HOST_$(1))/morestack.S
// this function, so we just codegen a generic reference to it. We don't
// specify any of the types for the function, we just make it a symbol
// that LLVM can later use.
+ //
+ // Note that MSVC is a little special here in that we don't use the
+ // `eh_personality` lang item at all. Currently LLVM has support for
+ // both Dwarf and SEH unwind mechanisms for MSVC targets and uses the
+ // *name of the personality function* to decide what kind of unwind side
+ // tables/landing pads to emit. It looks like Dwarf is used by default,
+ // injecting a dependency on the `_Unwind_Resume` symbol for resuming
+ // an "exception", but for MSVC we want to force SEH. This means that we
+ // can't actually have the personality function be our standard
+ // `rust_eh_personality` function, but rather we wired it up to the
+ // CRT's custom `__C_specific_handler` personality funciton, which
+ // forces LLVM to consider landing pads as "landing pads for SEH".
+ let target = &self.ccx.sess().target.target;
let llpersonality = match pad_bcx.tcx().lang_items.eh_personality() {
- Some(def_id) => {
+ Some(def_id) if !target.options.is_like_msvc => {
callee::trans_fn_ref(pad_bcx.ccx(), def_id, ExprId(0),
pad_bcx.fcx.param_substs).val
}
- None => {
+ _ => {
let mut personality = self.ccx.eh_personality().borrow_mut();
match *personality {
Some(llpersonality) => llpersonality,
None => {
+ let name = if target.options.is_like_msvc {
+ "__C_specific_handler"
+ } else {
+ "rust_eh_personality"
+ };
let fty = Type::variadic_func(&[], &Type::i32(self.ccx));
- let f = declare::declare_cfn(self.ccx, "rust_eh_personality", fty,
+ let f = declare::declare_cfn(self.ccx, name, fty,
self.ccx.tcx().types.i32);
*personality = Some(f);
f
// 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)
+//!
+//! 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.
+//!
+//! 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.
+//!
+//! 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.
+//! * 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`.
+//!
+//! So given all that, the bindings here are pretty small,
+
+#![allow(bad_style)]
+
use prelude::v1::*;
use any::Any;
-use intrinsics;
-use libc::c_void;
+use libc::{c_ulong, DWORD, c_void};
+use sys_common::thread_local::StaticKey;
+
+// 0x R U S T
+const RUST_PANIC: DWORD = 0x52555354;
+static PANIC_DATA: StaticKey = StaticKey::new(None);
+
+// This function is provided by kernel32.dll
+extern "system" {
+ fn RaiseException(dwExceptionCode: DWORD,
+ dwExceptionFlags: DWORD,
+ nNumberOfArguments: DWORD,
+ lpArguments: *const c_ulong);
+}
+
+#[repr(C)]
+pub struct EXCEPTION_POINTERS {
+ ExceptionRecord: *mut EXCEPTION_RECORD,
+ ContextRecord: *mut CONTEXT,
+}
+
+enum CONTEXT {}
+
+#[repr(C)]
+struct EXCEPTION_RECORD {
+ ExceptionCode: DWORD,
+ ExceptionFlags: DWORD,
+ ExceptionRecord: *mut _EXCEPTION_RECORD,
+ ExceptionAddress: *mut c_void,
+ NumberParameters: DWORD,
+ ExceptionInformation: [*mut c_ulong; EXCEPTION_MAXIMUM_PARAMETERS],
+}
-pub unsafe fn panic(_data: Box<Any + Send + 'static>) -> ! {
- intrinsics::abort();
+enum _EXCEPTION_RECORD {}
+
+const EXCEPTION_MAXIMUM_PARAMETERS: usize = 15;
+
+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);
+
+ RaiseException(RUST_PANIC, 0, 0, 0 as *const _);
+ rtabort!("could not unwind stack");
}
-pub unsafe fn cleanup(_ptr: *mut c_void) -> Box<Any + Send + 'static> {
- intrinsics::abort();
+pub unsafe fn cleanup(ptr: *mut c_void) -> 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 DWORD == RUST_PANIC);
+
+ let data = PANIC_DATA.get() as *mut Box<Any + Send + 'static>;
+ PANIC_DATA.set(0 as *mut u8);
+ rtassert!(!data.is_null());
+
+ *Box::from_raw(data)
}
+// 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.
#[lang = "eh_personality"]
-#[no_mangle]
-pub extern fn rust_eh_personality() {}
+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.
#[no_mangle]
-pub extern fn rust_eh_personality_catch() {}
+pub extern fn __rust_try_filter(eh_ptrs: *mut EXCEPTION_POINTERS,
+ _rbp: *mut c_void) -> i32 {
+ unsafe {
+ ((*(*eh_ptrs).ExceptionRecord).ExceptionCode == RUST_PANIC) as i32
+ }
+}
--- /dev/null
+; Copyright 2015 The Rust Project Developers. See the COPYRIGHT
+; file at the top-level directory of this distribution and at
+; http://rust-lang.org/COPYRIGHT.
+;
+; Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+; http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+; <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+; option. This file may not be copied, modified, or distributed
+; except according to those terms.
+
+; 64-bit MSVC's definition of the `rust_try` function. This function can't be
+; defined in Rust as it's a "try-catch" block that's not expressible in Rust's
+; syntax, so we're using LLVM to produce an object file with the associated
+; handler.
+;
+; To use the correct system implementation details, this file is separate from
+; the standard rust_try.ll as we need specifically use the __C_specific_handler
+; personality function or otherwise LLVM doesn't emit SEH handling tables.
+; There's also a few fiddly bits about SEH right now in LLVM that require us to
+; structure this a fairly particular way!
+;
+; See also: src/libstd/rt/unwind/seh.rs
+
+define i8* @rust_try(void (i8*)* %f, i8* %env) {
+ invoke void %f(i8* %env)
+ to label %normal
+ unwind label %catch
+
+normal:
+ ret i8* null
+
+; Here's where most of the magic happens, this is the only landing pad in rust
+; tagged with "catch" to indicate that we're catching an exception. The other
+; catch handlers in rust_try.ll just catch *all* exceptions, but that's because
+; most exceptions are already filtered out by their personality function.
+;
+; For MSVC we're just using a standard personality function that we can't
+; customize, so we need to do the exception filtering ourselves, and this is
+; currently performed by the `__rust_try_filter` function. This function,
+; specified in the landingpad instruction, will be invoked by Windows SEH
+; routines and will return whether the exception in question can be caught (aka
+; the Rust runtime is the one that threw the exception).
+;
+; To get this to compile (currently LLVM segfaults if it's not in this
+; particular structure), when the landingpad is executing we test to make sure
+; that the ID of the exception being thrown is indeed the one that we were
+; expecting. If it's not, we resume the exception, and otherwise we return the
+; pointer that we got
+;
+; Full disclosure: It's not clear to me what this `llvm.eh.typeid` stuff is
+; doing *other* then just allowing LLVM to compile this file without
+; segfaulting. I would expect the entire landing pad to just be:
+;
+; %vals = landingpad ...
+; %ehptr = extractvalue { i8*, i32 } %vals, 0
+; ret i8* %ehptr
+;
+; but apparently LLVM chokes on this, so we do the more complicated thing to
+; placate it.
+catch:
+ %vals = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)* @__C_specific_handler to i8*)
+ catch i8* bitcast (i32 (i8*, i8*)* @__rust_try_filter to i8*)
+ %ehptr = extractvalue { i8*, i32 } %vals, 0
+ %sel = extractvalue { i8*, i32 } %vals, 1
+ %filter_sel = call i32 @llvm.eh.typeid.for(i8* bitcast (i32 (i8*, i8*)* @__rust_try_filter to i8*))
+ %is_filter = icmp eq i32 %sel, %filter_sel
+ br i1 %is_filter, label %catch-return, label %catch-resume
+
+catch-return:
+ ret i8* %ehptr
+
+catch-resume:
+ resume { i8*, i32 } %vals
+}
+
+declare i32 @__C_specific_handler(...)
+declare i32 @__rust_try_filter(i8*, i8*)
+declare i32 @llvm.eh.typeid.for(i8*) readnone nounwind