Skip to content

Commit

Permalink
Implement unwinding for Win64.
Browse files Browse the repository at this point in the history
The original trick used to trigger unwinds would not work with GCC's implementation of SEH, so I had to invent a new one: rust_try now consists of two routines: the outer one, whose handler triggers unwinds, and the inner one, that stops unwinds by having a landing pad that swallows exceptions and passes them on to the outer routine via a normal return.
  • Loading branch information
vadimcn committed Aug 5, 2014
1 parent a12b235 commit 5a24ee8
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 35 deletions.
2 changes: 1 addition & 1 deletion src/librustrt/libunwind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ pub type _Unwind_Word = libc::uintptr_t;
pub static unwinder_private_data_size: uint = 5;

#[cfg(target_arch = "x86_64")]
pub static unwinder_private_data_size: uint = 2;
pub static unwinder_private_data_size: uint = 6;

#[cfg(target_arch = "arm", not(target_os = "ios"))]
pub static unwinder_private_data_size: uint = 20;
Expand Down
145 changes: 120 additions & 25 deletions src/librustrt/unwind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ fn rust_exception_class() -> uw::_Unwind_Exception_Class {
// This is achieved by overriding the return value in search phase to always
// say "catch!".

#[cfg(not(target_arch = "arm"), not(test))]
#[cfg(not(target_arch = "arm"), not(windows, target_arch = "x86_64"), not(test))]
#[doc(hidden)]
#[allow(visible_private_types)]
pub mod eabi {
Expand All @@ -244,7 +244,8 @@ pub mod eabi {
}

#[lang="eh_personality"]
extern fn eh_personality(
#[no_mangle] // referenced from rust_try.ll
extern fn rust_eh_personality(
version: c_int,
actions: uw::_Unwind_Action,
exception_class: uw::_Unwind_Exception_Class,
Expand All @@ -260,21 +261,19 @@ pub mod eabi {

#[no_mangle] // referenced from rust_try.ll
pub extern "C" fn rust_eh_personality_catch(
version: c_int,
_version: c_int,
actions: uw::_Unwind_Action,
exception_class: uw::_Unwind_Exception_Class,
ue_header: *mut uw::_Unwind_Exception,
context: *mut uw::_Unwind_Context
_exception_class: uw::_Unwind_Exception_Class,
_ue_header: *mut uw::_Unwind_Exception,
_context: *mut uw::_Unwind_Context
) -> uw::_Unwind_Reason_Code
{

if (actions as c_int & uw::_UA_SEARCH_PHASE as c_int) != 0 { // search phase
uw::_URC_HANDLER_FOUND // catch!
}
else { // cleanup phase
unsafe {
__gcc_personality_v0(version, actions, exception_class, ue_header,
context)
}
uw::_URC_INSTALL_CONTEXT
}
}
}
Expand All @@ -299,7 +298,7 @@ pub mod eabi {
}

#[lang="eh_personality"]
#[no_mangle] // so we can reference it by name from middle/trans/base.rs
#[no_mangle] // referenced from rust_try.ll
pub extern "C" fn rust_eh_personality(
version: c_int,
actions: uw::_Unwind_Action,
Expand All @@ -316,29 +315,26 @@ pub mod eabi {

#[no_mangle] // referenced from rust_try.ll
pub extern "C" fn rust_eh_personality_catch(
version: c_int,
_version: c_int,
actions: uw::_Unwind_Action,
exception_class: uw::_Unwind_Exception_Class,
ue_header: *mut uw::_Unwind_Exception,
context: *mut uw::_Unwind_Context
_exception_class: uw::_Unwind_Exception_Class,
_ue_header: *mut uw::_Unwind_Exception,
_context: *mut uw::_Unwind_Context
) -> uw::_Unwind_Reason_Code
{
if (actions as c_int & uw::_UA_SEARCH_PHASE as c_int) != 0 { // search phase
uw::_URC_HANDLER_FOUND // catch!
}
else { // cleanup phase
unsafe {
__gcc_personality_sj0(version, actions, exception_class, ue_header,
context)
}
uw::_URC_INSTALL_CONTEXT
}
}
}


// ARM EHABI uses a slightly different personality routine signature,
// but otherwise works the same.
#[cfg(target_arch = "arm", not(test), not(target_os = "ios"))]
#[cfg(target_arch = "arm", not(target_os = "ios", not(test)))]
#[allow(visible_private_types)]
pub mod eabi {
use uw = libunwind;
Expand All @@ -352,7 +348,8 @@ pub mod eabi {
}

#[lang="eh_personality"]
extern "C" fn eh_personality(
#[no_mangle] // referenced from rust_try.ll
extern "C" fn rust_eh_personality(
state: uw::_Unwind_State,
ue_header: *mut uw::_Unwind_Exception,
context: *mut uw::_Unwind_Context
Expand All @@ -366,19 +363,117 @@ pub mod eabi {
#[no_mangle] // referenced from rust_try.ll
pub extern "C" fn rust_eh_personality_catch(
state: uw::_Unwind_State,
ue_header: *mut uw::_Unwind_Exception,
context: *mut uw::_Unwind_Context
_ue_header: *mut uw::_Unwind_Exception,
_context: *mut uw::_Unwind_Context
) -> uw::_Unwind_Reason_Code
{
if (state as c_int & uw::_US_ACTION_MASK as c_int)
== uw::_US_VIRTUAL_UNWIND_FRAME as c_int { // search phase
uw::_URC_HANDLER_FOUND // catch!
}
else { // cleanup phase
unsafe {
__gcc_personality_v0(state, ue_header, context)
uw::_URC_INSTALL_CONTEXT
}
}
}

// Win64 SEH (see http://msdn.microsoft.com/en-us/library/1eyas8tf.aspx)
//
// This looks a bit convoluted because rather than implementing a native SEH handler,
// GCC reuses the same personality routine as for the other architectures by wrapping it
// with an "API translator" layer (_GCC_specific_handler).

#[cfg(windows, target_arch = "x86_64", not(test))]
#[allow(visible_private_types)]
#[allow(non_camel_case_types)]
#[allow(unused_variable)]
#[allow(uppercase_variables)]
pub mod eabi {
use uw = libunwind;
use libc::{c_void, c_int};

struct EXCEPTION_RECORD;
struct CONTEXT;
struct DISPATCHER_CONTEXT;

#[repr(C)]
enum EXCEPTION_DISPOSITION {
ExceptionContinueExecution,
ExceptionContinueSearch,
ExceptionNestedException,
ExceptionCollidedUnwind
}

type _Unwind_Personality_Fn =
extern "C" fn(
version: c_int,
actions: uw::_Unwind_Action,
exception_class: uw::_Unwind_Exception_Class,
ue_header: *mut uw::_Unwind_Exception,
context: *mut uw::_Unwind_Context
) -> uw::_Unwind_Reason_Code;

extern "C" {
fn __gcc_personality_seh0(
exceptionRecord: *mut EXCEPTION_RECORD,
establisherFrame: *mut c_void,
contextRecord: *mut CONTEXT,
dispatcherContext: *mut DISPATCHER_CONTEXT
) -> EXCEPTION_DISPOSITION;

fn _GCC_specific_handler(
exceptionRecord: *mut EXCEPTION_RECORD,
establisherFrame: *mut c_void,
contextRecord: *mut CONTEXT,
dispatcherContext: *mut DISPATCHER_CONTEXT,
personality: _Unwind_Personality_Fn
) -> EXCEPTION_DISPOSITION;
}

#[lang="eh_personality"]
#[no_mangle] // referenced from rust_try.ll
extern "C" fn rust_eh_personality(
exceptionRecord: *mut EXCEPTION_RECORD,
establisherFrame: *mut c_void,
contextRecord: *mut CONTEXT,
dispatcherContext: *mut DISPATCHER_CONTEXT
) -> EXCEPTION_DISPOSITION
{
unsafe {
__gcc_personality_seh0(exceptionRecord, establisherFrame,
contextRecord, dispatcherContext)
}
}

#[no_mangle] // referenced from rust_try.ll
pub extern "C" fn rust_eh_personality_catch(
exceptionRecord: *mut EXCEPTION_RECORD,
establisherFrame: *mut c_void,
contextRecord: *mut CONTEXT,
dispatcherContext: *mut DISPATCHER_CONTEXT
) -> EXCEPTION_DISPOSITION
{
extern "C" fn inner(
_version: c_int,
actions: uw::_Unwind_Action,
_exception_class: uw::_Unwind_Exception_Class,
_ue_header: *mut uw::_Unwind_Exception,
_context: *mut uw::_Unwind_Context
) -> uw::_Unwind_Reason_Code
{
if (actions as c_int & uw::_UA_SEARCH_PHASE as c_int) != 0 { // search phase
uw::_URC_HANDLER_FOUND // catch!
}
else { // cleanup phase
uw::_URC_INSTALL_CONTEXT
}
}

unsafe {
_GCC_specific_handler(exceptionRecord, establisherFrame,
contextRecord, dispatcherContext,
inner)
}
}
}

Expand Down
34 changes: 25 additions & 9 deletions src/rt/rust_try.ll
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,40 @@
; 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.
; See also: libstd/rt/unwind.rs
; See also: librustrt/unwind.rs

define i8* @rust_try(void (i8*,i8*)* %f, i8* %fptr, i8* %env) {

invoke void %f(i8* %fptr, i8* %env)
to label %normal
unwind label %catch
%1 = invoke i8* @rust_try_inner(void (i8*,i8*)* %f, i8* %fptr, i8* %env)
to label %normal
unwind label %catch

normal:
ret i8* null
ret i8* %1

catch:
%1 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)* @rust_eh_personality_catch to i8*)
catch i8* null ; catch everything
landingpad { i8*, i32 } personality i8* bitcast (i32 (...)* @rust_eh_personality_catch to i8*)
catch i8* null
; execution will never reach here because rust_try_inner's landing pad does not resume unwinds
ret i8* null
}

define internal i8* @rust_try_inner(void (i8*,i8*)* %f, i8* %fptr, i8* %env) {

invoke void %f(i8* %fptr, i8* %env)
to label %normal
unwind label %catch

; extract and return pointer to the exception object
normal:
ret i8* null

catch:
%1 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)* @rust_eh_personality to i8*)
catch i8* null
; extract and return pointer to the exception object
%2 = extractvalue { i8*, i32 } %1, 0
ret i8* %2
ret i8* %2
}

declare i32 @rust_eh_personality(...)
declare i32 @rust_eh_personality_catch(...)

0 comments on commit 5a24ee8

Please sign in to comment.