Skip to content

Commit 91d799e

Browse files
committed
msvc: Implement runtime support for unwinding
Now that LLVM has been updated, the only remaining roadblock to implementing unwinding for MSVC is to fill out the runtime support in `std::rt::unwind::seh`. This commit does precisely that, fixing up some other bits and pieces along the way: * The `seh` unwinding module now uses `RaiseException` to initiate a panic. * The `rust_try.ll` file was rewritten for MSVC (as it's quite different) and is located at `rust_try_msvc_64.ll`, only included on MSVC builds for now. * The personality function for all landing pads generated by LLVM is hard-wired to `__C_specific_handler` instead of the standard `rust_eh_personality` lang item. This is required to get LLVM to emit SEH unwinding information instead of DWARF unwinding information. This also means that on MSVC the `rust_eh_personality` function is entirely unused (but is defined as it's a lang item). More details about how panicking works on SEH can be found in the `rust_try_msvc_64.ll` or `seh.rs` files, but I'm always open to adding more comments! A key aspect of this PR is missing, however, which is that **unwinding is still turned off by default for MSVC**. There is a [bug in llvm][llvm-bug] which causes optimizations to inline enough landing pads that LLVM chokes. If the compiler is optimized at `-O1` (where inlining isn't enabled) then it can bootstrap with unwinding enabled, but when optimized at `-O2` (inlining is enabled) then it hits a fatal LLVM error. [llvm-bug]: https://llvm.org/bugs/show_bug.cgi?id=23884
1 parent 1ec520a commit 91d799e

File tree

4 files changed

+227
-15
lines changed

4 files changed

+227
-15
lines changed

mk/rt.mk

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,12 @@ NATIVE_DEPS_hoedown_$(1) := hoedown/src/autolink.c \
5353
NATIVE_DEPS_miniz_$(1) = miniz.c
5454
NATIVE_DEPS_rust_builtin_$(1) := rust_builtin.c \
5555
rust_android_dummy.c
56-
NATIVE_DEPS_rustrt_native_$(1) := \
57-
rust_try.ll \
58-
arch/$$(HOST_$(1))/record_sp.S
56+
NATIVE_DEPS_rustrt_native_$(1) := arch/$$(HOST_$(1))/record_sp.S
57+
ifeq ($$(findstring msvc,$(1)),msvc)
58+
NATIVE_DEPS_rustrt_native_$(1) += rust_try_msvc_64.ll
59+
else
60+
NATIVE_DEPS_rustrt_native_$(1) += rust_try.ll
61+
endif
5962
NATIVE_DEPS_rust_test_helpers_$(1) := rust_test_helpers.c
6063
NATIVE_DEPS_morestack_$(1) := arch/$$(HOST_$(1))/morestack.S
6164

src/librustc_trans/trans/cleanup.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -856,18 +856,36 @@ impl<'blk, 'tcx> CleanupHelperMethods<'blk, 'tcx> for FunctionContext<'blk, 'tcx
856856
// this function, so we just codegen a generic reference to it. We don't
857857
// specify any of the types for the function, we just make it a symbol
858858
// that LLVM can later use.
859+
//
860+
// Note that MSVC is a little special here in that we don't use the
861+
// `eh_personality` lang item at all. Currently LLVM has support for
862+
// both Dwarf and SEH unwind mechanisms for MSVC targets and uses the
863+
// *name of the personality function* to decide what kind of unwind side
864+
// tables/landing pads to emit. It looks like Dwarf is used by default,
865+
// injecting a dependency on the `_Unwind_Resume` symbol for resuming
866+
// an "exception", but for MSVC we want to force SEH. This means that we
867+
// can't actually have the personality function be our standard
868+
// `rust_eh_personality` function, but rather we wired it up to the
869+
// CRT's custom `__C_specific_handler` personality funciton, which
870+
// forces LLVM to consider landing pads as "landing pads for SEH".
871+
let target = &self.ccx.sess().target.target;
859872
let llpersonality = match pad_bcx.tcx().lang_items.eh_personality() {
860-
Some(def_id) => {
873+
Some(def_id) if !target.options.is_like_msvc => {
861874
callee::trans_fn_ref(pad_bcx.ccx(), def_id, ExprId(0),
862875
pad_bcx.fcx.param_substs).val
863876
}
864-
None => {
877+
_ => {
865878
let mut personality = self.ccx.eh_personality().borrow_mut();
866879
match *personality {
867880
Some(llpersonality) => llpersonality,
868881
None => {
882+
let name = if target.options.is_like_msvc {
883+
"__C_specific_handler"
884+
} else {
885+
"rust_eh_personality"
886+
};
869887
let fty = Type::variadic_func(&[], &Type::i32(self.ccx));
870-
let f = declare::declare_cfn(self.ccx, "rust_eh_personality", fty,
888+
let f = declare::declare_cfn(self.ccx, name, fty,
871889
self.ccx.tcx().types.i32);
872890
*personality = Some(f);
873891
f

src/libstd/rt/unwind/seh.rs

Lines changed: 122 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,136 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11+
//! Win64 SEH (see http://msdn.microsoft.com/en-us/library/1eyas8tf.aspx)
12+
//!
13+
//! On Windows (currently only on MSVC), the default exception handling
14+
//! mechanism is Structured Exception Handling (SEH). This is quite different
15+
//! than Dwarf-based exception handling (e.g. what other unix platforms use) in
16+
//! terms of compiler internals, so LLVM is required to have a good deal of
17+
//! extra support for SEH. Currently this support is somewhat lacking, so what's
18+
//! here is the bare bones of SEH support.
19+
//!
20+
//! In a nutshell, what happens here is:
21+
//!
22+
//! 1. The `panic` function calls the standard Windows function `RaiseException`
23+
//! with a Rust-specific code, triggering the unwinding process.
24+
//! 2. All landing pads generated by the compiler (just "cleanup" landing pads)
25+
//! use the personality function `__C_specific_handler`, a function in the
26+
//! CRT, and the unwinding code in Windows will use this personality function
27+
//! to execute all cleanup code on the stack.
28+
//! 3. Eventually the "catch" code in `rust_try` (located in
29+
//! src/rt/rust_try_msvc_64.ll) is executed, which will ensure that the
30+
//! exception being caught is indeed a Rust exception, returning control back
31+
//! into Rust.
32+
//!
33+
//! Some specific differences from the gcc-based exception handling are:
34+
//!
35+
//! * Rust has no custom personality function, it is instead *always*
36+
//! __C_specific_handler, so the filtering is done in a C++-like manner
37+
//! instead of in the personality function itself. Note that the specific
38+
//! syntax for this (found in the rust_try_msvc_64.ll) is taken from an LLVM
39+
//! test case for SEH.
40+
//! * We've got some data to transmit across the unwinding boundary,
41+
//! specifically a `Box<Any + Send + 'static>`. In Dwarf-based unwinding this
42+
//! data is part of the payload of the exception, but I have not currently
43+
//! figured out how to do this with LLVM's bindings. Judging by some comments
44+
//! in the LLVM test cases this may not even be possible currently with LLVM,
45+
//! so this is just abandoned entirely. Instead the data is stored in a
46+
//! thread-local in `panic` and retrieved during `cleanup`.
47+
//!
48+
//! So given all that, the bindings here are pretty small,
49+
50+
#![allow(bad_style)]
51+
1152
use prelude::v1::*;
1253

1354
use any::Any;
14-
use intrinsics;
15-
use libc::c_void;
55+
use libc::{c_ulong, DWORD, c_void};
56+
use sys_common::thread_local::StaticKey;
57+
58+
// 0x R U S T
59+
const RUST_PANIC: DWORD = 0x52555354;
60+
static PANIC_DATA: StaticKey = StaticKey::new(None);
61+
62+
// This function is provided by kernel32.dll
63+
extern "system" {
64+
fn RaiseException(dwExceptionCode: DWORD,
65+
dwExceptionFlags: DWORD,
66+
nNumberOfArguments: DWORD,
67+
lpArguments: *const c_ulong);
68+
}
69+
70+
#[repr(C)]
71+
pub struct EXCEPTION_POINTERS {
72+
ExceptionRecord: *mut EXCEPTION_RECORD,
73+
ContextRecord: *mut CONTEXT,
74+
}
75+
76+
enum CONTEXT {}
77+
78+
#[repr(C)]
79+
struct EXCEPTION_RECORD {
80+
ExceptionCode: DWORD,
81+
ExceptionFlags: DWORD,
82+
ExceptionRecord: *mut _EXCEPTION_RECORD,
83+
ExceptionAddress: *mut c_void,
84+
NumberParameters: DWORD,
85+
ExceptionInformation: [*mut c_ulong; EXCEPTION_MAXIMUM_PARAMETERS],
86+
}
1687

17-
pub unsafe fn panic(_data: Box<Any + Send + 'static>) -> ! {
18-
intrinsics::abort();
88+
enum _EXCEPTION_RECORD {}
89+
90+
const EXCEPTION_MAXIMUM_PARAMETERS: usize = 15;
91+
92+
pub unsafe fn panic(data: Box<Any + Send + 'static>) -> ! {
93+
// See module docs above for an explanation of why `data` is stored in a
94+
// thread local instead of being passed as an argument to the
95+
// `RaiseException` function (which can in theory carry along arbitrary
96+
// data).
97+
let exception = Box::new(data);
98+
rtassert!(PANIC_DATA.get().is_null());
99+
PANIC_DATA.set(Box::into_raw(exception) as *mut u8);
100+
101+
RaiseException(RUST_PANIC, 0, 0, 0 as *const _);
102+
rtabort!("could not unwind stack");
19103
}
20104

21-
pub unsafe fn cleanup(_ptr: *mut c_void) -> Box<Any + Send + 'static> {
22-
intrinsics::abort();
105+
pub unsafe fn cleanup(ptr: *mut c_void) -> Box<Any + Send + 'static> {
106+
// The `ptr` here actually corresponds to the code of the exception, and our
107+
// real data is stored in our thread local.
108+
rtassert!(ptr as DWORD == RUST_PANIC);
109+
110+
let data = PANIC_DATA.get() as *mut Box<Any + Send + 'static>;
111+
PANIC_DATA.set(0 as *mut u8);
112+
rtassert!(!data.is_null());
113+
114+
*Box::from_raw(data)
23115
}
24116

117+
// This is required by the compiler to exist (e.g. it's a lang item), but it's
118+
// never actually called by the compiler because __C_specific_handler is the
119+
// personality function that is always used. Hence this is just an aborting
120+
// stub.
25121
#[lang = "eh_personality"]
26-
#[no_mangle]
27-
pub extern fn rust_eh_personality() {}
122+
fn rust_eh_personality() {
123+
unsafe { ::intrinsics::abort() }
124+
}
28125

126+
// This is a function referenced from `rust_try_msvc_64.ll` which is used to
127+
// filter the exceptions being caught by that function.
128+
//
129+
// In theory local variables can be accessed through the `rbp` parameter of this
130+
// function, but a comment in an LLVM test case indicates that this is not
131+
// implemented in LLVM, so this is just an idempotent function which doesn't
132+
// ferry along any other information.
133+
//
134+
// This function just takes a look at the current EXCEPTION_RECORD being thrown
135+
// to ensure that it's code is RUST_PANIC, which was set by the call to
136+
// `RaiseException` above in the `panic` function.
29137
#[no_mangle]
30-
pub extern fn rust_eh_personality_catch() {}
138+
pub extern fn __rust_try_filter(eh_ptrs: *mut EXCEPTION_POINTERS,
139+
_rbp: *mut c_void) -> i32 {
140+
unsafe {
141+
((*(*eh_ptrs).ExceptionRecord).ExceptionCode == RUST_PANIC) as i32
142+
}
143+
}

src/rt/rust_try_msvc_64.ll

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
; Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
; file at the top-level directory of this distribution and at
3+
; http://rust-lang.org/COPYRIGHT.
4+
;
5+
; Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
; http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
; <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
; option. This file may not be copied, modified, or distributed
9+
; except according to those terms.
10+
11+
; 64-bit MSVC's definition of the `rust_try` function. This function can't be
12+
; defined in Rust as it's a "try-catch" block that's not expressible in Rust's
13+
; syntax, so we're using LLVM to produce an object file with the associated
14+
; handler.
15+
;
16+
; To use the correct system implementation details, this file is separate from
17+
; the standard rust_try.ll as we need specifically use the __C_specific_handler
18+
; personality function or otherwise LLVM doesn't emit SEH handling tables.
19+
; There's also a few fiddly bits about SEH right now in LLVM that require us to
20+
; structure this a fairly particular way!
21+
;
22+
; See also: src/libstd/rt/unwind/seh.rs
23+
24+
define i8* @rust_try(void (i8*)* %f, i8* %env) {
25+
invoke void %f(i8* %env)
26+
to label %normal
27+
unwind label %catch
28+
29+
normal:
30+
ret i8* null
31+
32+
; Here's where most of the magic happens, this is the only landing pad in rust
33+
; tagged with "catch" to indicate that we're catching an exception. The other
34+
; catch handlers in rust_try.ll just catch *all* exceptions, but that's because
35+
; most exceptions are already filtered out by their personality function.
36+
;
37+
; For MSVC we're just using a standard personality function that we can't
38+
; customize, so we need to do the exception filtering ourselves, and this is
39+
; currently performed by the `__rust_try_filter` function. This function,
40+
; specified in the landingpad instruction, will be invoked by Windows SEH
41+
; routines and will return whether the exception in question can be caught (aka
42+
; the Rust runtime is the one that threw the exception).
43+
;
44+
; To get this to compile (currently LLVM segfaults if it's not in this
45+
; particular structure), when the landingpad is executing we test to make sure
46+
; that the ID of the exception being thrown is indeed the one that we were
47+
; expecting. If it's not, we resume the exception, and otherwise we return the
48+
; pointer that we got
49+
;
50+
; Full disclosure: It's not clear to me what this `llvm.eh.typeid` stuff is
51+
; doing *other* then just allowing LLVM to compile this file without
52+
; segfaulting. I would expect the entire landing pad to just be:
53+
;
54+
; %vals = landingpad ...
55+
; %ehptr = extractvalue { i8*, i32 } %vals, 0
56+
; ret i8* %ehptr
57+
;
58+
; but apparently LLVM chokes on this, so we do the more complicated thing to
59+
; placate it.
60+
catch:
61+
%vals = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)* @__C_specific_handler to i8*)
62+
catch i8* bitcast (i32 (i8*, i8*)* @__rust_try_filter to i8*)
63+
%ehptr = extractvalue { i8*, i32 } %vals, 0
64+
%sel = extractvalue { i8*, i32 } %vals, 1
65+
%filter_sel = call i32 @llvm.eh.typeid.for(i8* bitcast (i32 (i8*, i8*)* @__rust_try_filter to i8*))
66+
%is_filter = icmp eq i32 %sel, %filter_sel
67+
br i1 %is_filter, label %catch-return, label %catch-resume
68+
69+
catch-return:
70+
ret i8* %ehptr
71+
72+
catch-resume:
73+
resume { i8*, i32 } %vals
74+
}
75+
76+
declare i32 @__C_specific_handler(...)
77+
declare i32 @__rust_try_filter(i8*, i8*)
78+
declare i32 @llvm.eh.typeid.for(i8*) readnone nounwind

0 commit comments

Comments
 (0)