Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Win64 eh_personality natively. #27210

Merged
merged 3 commits into from
Aug 3, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/doc/trpl/lang-items.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ fn main(argc: isize, argv: *const *const u8) -> isize {
#[lang = "stack_exhausted"] extern fn stack_exhausted() {}
#[lang = "eh_personality"] extern fn eh_personality() {}
#[lang = "panic_fmt"] fn panic_fmt() -> ! { loop {} }
# #[lang = "eh_unwind_resume"] extern fn rust_eh_unwind_resume() {}
```

Note the use of `abort`: the `exchange_malloc` lang item is assumed to
Expand Down
3 changes: 3 additions & 0 deletions src/doc/trpl/no-stdlib.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ fn start(_argc: isize, _argv: *const *const u8) -> isize {
#[lang = "stack_exhausted"] extern fn stack_exhausted() {}
#[lang = "eh_personality"] extern fn eh_personality() {}
#[lang = "panic_fmt"] fn panic_fmt() -> ! { loop {} }
# #[lang = "eh_unwind_resume"] extern fn rust_eh_unwind_resume() {}
# // fn main() {} tricked you, rustdoc!
```

Expand All @@ -63,6 +64,7 @@ pub extern fn main(argc: i32, argv: *const *const u8) -> i32 {
#[lang = "stack_exhausted"] extern fn stack_exhausted() {}
#[lang = "eh_personality"] extern fn eh_personality() {}
#[lang = "panic_fmt"] fn panic_fmt() -> ! { loop {} }
# #[lang = "eh_unwind_resume"] extern fn rust_eh_unwind_resume() {}
# // fn main() {} tricked you, rustdoc!
```

Expand Down Expand Up @@ -150,6 +152,7 @@ extern fn panic_fmt(args: &core::fmt::Arguments,

#[lang = "stack_exhausted"] extern fn stack_exhausted() {}
#[lang = "eh_personality"] extern fn eh_personality() {}
# #[lang = "eh_unwind_resume"] extern fn rust_eh_unwind_resume() {}
# #[start] fn start(argc: isize, argv: *const *const u8) -> isize { 0 }
# fn main() {}
```
Expand Down
1 change: 1 addition & 0 deletions src/librustc/middle/lang_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ lets_do_this! {

EhPersonalityLangItem, "eh_personality", eh_personality;
EhPersonalityCatchLangItem, "eh_personality_catch", eh_personality_catch;
EhUnwindResumeLangItem, "eh_unwind_resume", eh_unwind_resume;
MSVCTryFilterLangItem, "msvc_try_filter", msvc_try_filter;

ExchangeHeapLangItem, "exchange_heap", exchange_heap;
Expand Down
5 changes: 5 additions & 0 deletions src/librustc/middle/weak_lang_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ pub fn check_crate(krate: &ast::Crate,
if items.eh_personality().is_none() {
items.missing.push(lang_items::EhPersonalityLangItem);
}
if sess.target.target.options.custom_unwind_resume &
items.eh_unwind_resume().is_none() {
items.missing.push(lang_items::EhUnwindResumeLangItem);
}

{
let mut cx = Context { sess: sess, items: items };
Expand Down Expand Up @@ -122,4 +126,5 @@ weak_lang_items! {
panic_fmt, PanicFmtLangItem, rust_begin_unwind;
stack_exhausted, StackExhaustedLangItem, rust_stack_exhausted;
eh_personality, EhPersonalityLangItem, rust_eh_personality;
eh_unwind_resume, EhUnwindResumeLangItem, rust_eh_unwind_resume;
}
6 changes: 6 additions & 0 deletions src/librustc_back/target/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,11 @@ pub struct TargetOptions {
/// currently only "gnu" is used to fall into LLVM. Unknown strings cause
/// the system linker to be used.
pub archive_format: String,
/// Whether the target uses a custom unwind resumption routine.
/// By default LLVM lowers `resume` instructions into calls to `_Unwind_Resume`
/// defined in libgcc. If this option is enabled, the target must provide
/// `eh_unwind_resume` lang item.
pub custom_unwind_resume: bool,
}

impl Default for TargetOptions {
Expand Down Expand Up @@ -209,6 +214,7 @@ impl Default for TargetOptions {
pre_link_objects: Vec::new(),
post_link_objects: Vec::new(),
archive_format: String::new(),
custom_unwind_resume: false,
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/librustc_back/target/x86_64_pc_windows_gnu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub fn target() -> Target {
// On Win64 unwinding is handled by the OS, so we can link libgcc statically.
base.pre_link_args.push("-static-libgcc".to_string());
base.pre_link_args.push("-m64".to_string());
base.custom_unwind_resume = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this also set the hook for x86_64-pc-windows-msvc?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking of a separate PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, sounds good to me


Target {
llvm_target: "x86_64-pc-windows-gnu".to_string(),
Expand Down
6 changes: 6 additions & 0 deletions src/librustc_trans/trans/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2171,6 +2171,12 @@ fn finish_register_fn(ccx: &CrateContext, sym: String, node_id: ast::NodeId,
llvm::SetDLLStorageClass(llfn, llvm::DLLExportStorageClass);
}
}
if ccx.tcx().lang_items.eh_unwind_resume() == Some(def) {
llvm::SetLinkage(llfn, llvm::ExternalLinkage);
if ccx.use_dll_storage_attrs() {
llvm::SetDLLStorageClass(llfn, llvm::DLLExportStorageClass);
}
}
}

fn register_fn<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
Expand Down
2 changes: 2 additions & 0 deletions src/librustc_trans/trans/cleanup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,8 @@ impl<'blk, 'tcx> CleanupHelperMethods<'blk, 'tcx> for FunctionContext<'blk, 'tcx

debug!("get_or_create_landing_pad");

self.inject_unwind_resume_hook();

// Check if a landing pad block exists; if not, create one.
{
let mut scopes = self.scopes.borrow_mut();
Expand Down
49 changes: 49 additions & 0 deletions src/librustc_trans/trans/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,55 @@ impl<'a, 'tcx> FunctionContext<'a, 'tcx> {
}
}
}

/// By default, LLVM lowers `resume` instructions into calls to `_Unwind_Resume`
/// defined in libgcc, however, unlike personality routines, there is no easy way to
/// override that symbol. This method injects a local-scoped `_Unwind_Resume` function
/// which immediately defers to the user-defined `eh_unwind_resume` lang item.
pub fn inject_unwind_resume_hook(&self) {
let ccx = self.ccx;
if !ccx.sess().target.target.options.custom_unwind_resume ||
ccx.unwind_resume_hooked().get() {
return;
}

let new_resume = match ccx.tcx().lang_items.eh_unwind_resume() {
Some(did) => callee::trans_fn_ref(ccx, did, ExprId(0), &self.param_substs).val,
None => {
let fty = Type::variadic_func(&[], &Type::void(self.ccx));
declare::declare_cfn(self.ccx, "rust_eh_unwind_resume", fty,
self.ccx.tcx().mk_nil())
}
};

unsafe {
let resume_type = Type::func(&[Type::i8(ccx).ptr_to()], &Type::void(ccx));
let old_resume = llvm::LLVMAddFunction(ccx.llmod(),
"_Unwind_Resume\0".as_ptr() as *const _,
resume_type.to_ref());
llvm::SetLinkage(old_resume, llvm::InternalLinkage);
let llbb = llvm::LLVMAppendBasicBlockInContext(ccx.llcx(),
old_resume,
"\0".as_ptr() as *const _);
let builder = ccx.builder();
builder.position_at_end(llbb);
builder.call(new_resume, &[llvm::LLVMGetFirstParam(old_resume)], None);
builder.unreachable(); // it should never return

// Until DwarfEHPrepare pass has run, _Unwind_Resume is not referenced by any live code
// and is subject to dead code elimination. Here we add _Unwind_Resume to @llvm.globals
// to prevent that.
let i8p_ty = Type::i8p(ccx);
let used_ty = Type::array(&i8p_ty, 1);
let used = llvm::LLVMAddGlobal(ccx.llmod(), used_ty.to_ref(),
"llvm.used\0".as_ptr() as *const _);
let old_resume = llvm::LLVMConstBitCast(old_resume, i8p_ty.to_ref());
llvm::LLVMSetInitializer(used, C_array(i8p_ty, &[old_resume]));
llvm::SetLinkage(used, llvm::AppendingLinkage);
llvm::LLVMSetSection(used, "llvm.metadata\0".as_ptr() as *const _)
}
ccx.unwind_resume_hooked().set(true);
}
}

// Basic block context. We create a block context for each basic block
Expand Down
6 changes: 6 additions & 0 deletions src/librustc_trans/trans/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ pub struct LocalCrateContext<'tcx> {

eh_personality: RefCell<Option<ValueRef>>,
rust_try_fn: RefCell<Option<ValueRef>>,
unwind_resume_hooked: Cell<bool>,

intrinsics: RefCell<FnvHashMap<&'static str, ValueRef>>,

Expand Down Expand Up @@ -466,6 +467,7 @@ impl<'tcx> LocalCrateContext<'tcx> {
dbg_cx: dbg_cx,
eh_personality: RefCell::new(None),
rust_try_fn: RefCell::new(None),
unwind_resume_hooked: Cell::new(false),
intrinsics: RefCell::new(FnvHashMap()),
n_llvm_insns: Cell::new(0),
trait_cache: RefCell::new(FnvHashMap()),
Expand Down Expand Up @@ -735,6 +737,10 @@ impl<'b, 'tcx> CrateContext<'b, 'tcx> {
&self.local.rust_try_fn
}

pub fn unwind_resume_hooked<'a>(&'a self) -> &'a Cell<bool> {
&self.local.unwind_resume_hooked
}

fn intrinsics<'a>(&'a self) -> &'a RefCell<FnvHashMap<&'static str, ValueRef>> {
&self.local.intrinsics
}
Expand Down
151 changes: 47 additions & 104 deletions src/librustc_trans/trans/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use llvm::{SequentiallyConsistent, Acquire, Release, AtomicXchg, ValueRef, TypeK
use middle::subst;
use middle::subst::FnSpace;
use trans::adt;
use trans::attributes;
use trans::base::*;
use trans::build::*;
use trans::callee;
Expand Down Expand Up @@ -1159,26 +1160,14 @@ fn trans_msvc_try<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
// of exceptions (e.g. the normal semantics of LLVM's landingpad and invoke
// instructions).
//
// This translation is a little surprising for two reasons:
// This translation is a little surprising because
// we always call a shim function instead of inlining the call to `invoke`
// manually here. This is done because in LLVM we're only allowed to have one
// personality per function definition. The call to the `try` intrinsic is
// being inlined into the function calling it, and that function may already
// have other personality functions in play. By calling a shim we're
// guaranteed that our shim will have the right personality function.
//
// 1. We always call a shim function instead of inlining the call to `invoke`
// manually here. This is done because in LLVM we're only allowed to have one
// personality per function definition. The call to the `try` intrinsic is
// being inlined into the function calling it, and that function may already
// have other personality functions in play. By calling a shim we're
// guaranteed that our shim will have the right personality function.
//
// 2. Instead of making one shim (explained above), we make two shims! The
// reason for this has to do with the technical details about the
// implementation of unwinding in the runtime, but the tl;dr; is that the
// outer shim's personality function says "catch rust exceptions" and the
// inner shim's landing pad will not `resume` the exception being thrown.
// This means that the outer shim's landing pad is never run and the inner
// shim's return value is the return value of the whole call.
//
// The double-shim aspect is currently done for implementation ease on the
// runtime side of things, and more info can be found in
// src/libstd/rt/unwind/gcc.rs.
fn trans_gnu_try<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
func: ValueRef,
data: ValueRef,
Expand All @@ -1188,108 +1177,63 @@ fn trans_gnu_try<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
let ccx = bcx.ccx();
let dloc = DebugLoc::None;

// Type indicator for the exception being thrown, not entirely sure
// what's going on here but it's what all the examples in LLVM use.
let lpad_ty = Type::struct_(ccx, &[Type::i8p(ccx), Type::i32(ccx)],
false);
// Translates the shims described above:
//
// bcx:
// invoke %func(%args...) normal %normal unwind %catch
//
// normal:
// ret null
//
// catch:
// (ptr, _) = landingpad
// ret ptr

// Define the "inner try" shim
let rust_try_inner = declare::define_internal_rust_fn(ccx,
"__rust_try_inner",
try_fn_ty);
trans_rust_try(ccx, rust_try_inner, lpad_ty, bcx.fcx.eh_personality(),
output, dloc, &mut |bcx, then, catch| {
let func = llvm::get_param(rust_try_inner, 0);
let data = llvm::get_param(rust_try_inner, 1);
Invoke(bcx, func, &[data], then.llbb, catch.llbb, None, dloc);
C_null(Type::i8p(ccx))
});

// Define the "outer try" shim.
let rust_try = declare::define_internal_rust_fn(ccx, "__rust_try",
try_fn_ty);
let rust_try = declare::define_internal_rust_fn(ccx, "__rust_try", try_fn_ty);
attributes::emit_uwtable(rust_try, true);
let catch_pers = match bcx.tcx().lang_items.eh_personality_catch() {
Some(did) => callee::trans_fn_ref(ccx, did, ExprId(0),
bcx.fcx.param_substs).val,
None => bcx.tcx().sess.bug("eh_personality_catch not defined"),
};
trans_rust_try(ccx, rust_try, lpad_ty, catch_pers, output, dloc,
&mut |bcx, then, catch| {
let func = llvm::get_param(rust_try, 0);
let data = llvm::get_param(rust_try, 1);
Invoke(bcx, rust_try_inner, &[func, data], then.llbb, catch.llbb,
None, dloc)
});
return rust_try
});

// Note that no invoke is used here because by definition this function
// can't panic (that's what it's catching).
let ret = Call(bcx, llfn, &[func, data], None, dloc);
Store(bcx, ret, dest);
return bcx;

// Translates both the inner and outer shims described above. The only
// difference between these two is the function invoked and the personality
// involved, so a common routine is shared.
//
// bcx:
// invoke %func(%args...) normal %normal unwind %unwind
//
// normal:
// ret null
//
// unwind:
// (ptr, _) = landingpad
// br (ptr != null), done, reraise
//
// done:
// ret ptr
//
// reraise:
// resume
//
// Note that the branch checking for `null` here isn't actually necessary,
// it's just an unfortunate hack to make sure that LLVM doesn't optimize too
// much. If this were not present, then LLVM would correctly deduce that our
// inner shim should be tagged with `nounwind` (as it catches all
// exceptions) and then the outer shim's `invoke` will be translated to just
// a simple call, destroying that entry for the personality function.
//
// To ensure that both shims always have an `invoke` this check against null
// confuses LLVM enough to the point that it won't infer `nounwind` and
// we'll proceed as normal.
fn trans_rust_try<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
llfn: ValueRef,
lpad_ty: Type,
personality: ValueRef,
output: ty::FnOutput<'tcx>,
dloc: DebugLoc,
invoke: &mut FnMut(Block, Block, Block) -> ValueRef) {
let (fcx, block_arena);
block_arena = TypedArena::new();
fcx = new_fn_ctxt(ccx, llfn, ast::DUMMY_NODE_ID, false,
fcx = new_fn_ctxt(ccx, rust_try, ast::DUMMY_NODE_ID, false,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're feeling ambitious it looks like the _Unwind_Resume shim you created above is much nicer to use than creating all these contexts here, so it may be easier to do that here as well. That being said it's just cleanup so feel free to pass!

output, ccx.tcx().mk_substs(Substs::trans_empty()),
None, &block_arena);
let bcx = init_function(&fcx, true, output);
let then = bcx.fcx.new_temp_block("then");
let catch = bcx.fcx.new_temp_block("catch");
let reraise = bcx.fcx.new_temp_block("reraise");
let catch_return = bcx.fcx.new_temp_block("catch-return");

let invoke_ret = invoke(bcx, then, catch);
Ret(then, invoke_ret, dloc);
let vals = LandingPad(catch, lpad_ty, personality, 1);
let func = llvm::get_param(rust_try, 0);
let data = llvm::get_param(rust_try, 1);
Invoke(bcx, func, &[data], then.llbb, catch.llbb, None, dloc);
Ret(then, C_null(Type::i8p(ccx)), dloc);

// Type indicator for the exception being thrown.
// The first value in this tuple is a pointer to the exception object being thrown.
// The second value is a "selector" indicating which of the landing pad clauses
// the exception's type had been matched to. rust_try ignores the selector.
let lpad_ty = Type::struct_(ccx, &[Type::i8p(ccx), Type::i32(ccx)],
false);
let vals = LandingPad(catch, lpad_ty, catch_pers, 1);
AddClause(catch, vals, C_null(Type::i8p(ccx)));
let ptr = ExtractValue(catch, vals, 0);
let valid = ICmp(catch, llvm::IntNE, ptr, C_null(Type::i8p(ccx)), dloc);
CondBr(catch, valid, catch_return.llbb, reraise.llbb, dloc);
Ret(catch_return, ptr, dloc);
Resume(reraise, vals);
}
Ret(catch, ptr, dloc);
fcx.cleanup();

return rust_try
});

// Note that no invoke is used here because by definition this function
// can't panic (that's what it's catching).
let ret = Call(bcx, llfn, &[func, data], None, dloc);
Store(bcx, ret, dest);
return bcx;
}

// Helper to generate the `Ty` associated with `rust_Try`
// Helper to generate the `Ty` associated with `rust_try`
fn get_rust_try_fn<'a, 'tcx>(fcx: &FunctionContext<'a, 'tcx>,
f: &mut FnMut(Ty<'tcx>,
ty::FnOutput<'tcx>) -> ValueRef)
Expand All @@ -1299,8 +1243,7 @@ fn get_rust_try_fn<'a, 'tcx>(fcx: &FunctionContext<'a, 'tcx>,
return llfn
}

// Define the types up front for the signatures of the rust_try and
// rust_try_inner functions.
// Define the type up front for the signature of the rust_try function.
let tcx = ccx.tcx();
let i8p = tcx.mk_mut_ptr(tcx.types.i8);
let fn_ty = tcx.mk_bare_fn(ty::BareFnTy {
Expand Down
Loading