diff --git a/src/doc/trpl/lang-items.md b/src/doc/trpl/lang-items.md
index 8e7504c2f18ea..39de8920f098c 100644
--- a/src/doc/trpl/lang-items.md
+++ b/src/doc/trpl/lang-items.md
@@ -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
diff --git a/src/doc/trpl/no-stdlib.md b/src/doc/trpl/no-stdlib.md
index 0a985334b5e4b..e530a9f1051a5 100644
--- a/src/doc/trpl/no-stdlib.md
+++ b/src/doc/trpl/no-stdlib.md
@@ -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!
```
@@ -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!
```
@@ -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() {}
```
diff --git a/src/librustc/middle/lang_items.rs b/src/librustc/middle/lang_items.rs
index f7cd94f30af12..da1b9f48eda72 100644
--- a/src/librustc/middle/lang_items.rs
+++ b/src/librustc/middle/lang_items.rs
@@ -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;
diff --git a/src/librustc/middle/weak_lang_items.rs b/src/librustc/middle/weak_lang_items.rs
index 72fda9a7ae06a..934f7c0688c17 100644
--- a/src/librustc/middle/weak_lang_items.rs
+++ b/src/librustc/middle/weak_lang_items.rs
@@ -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 };
@@ -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;
}
diff --git a/src/librustc_back/target/mod.rs b/src/librustc_back/target/mod.rs
index 39e42913ff674..ce05a8878ff4b 100644
--- a/src/librustc_back/target/mod.rs
+++ b/src/librustc_back/target/mod.rs
@@ -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 {
@@ -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,
}
}
}
diff --git a/src/librustc_back/target/x86_64_pc_windows_gnu.rs b/src/librustc_back/target/x86_64_pc_windows_gnu.rs
index e4d7b4bc9b024..aef1d7471b85b 100644
--- a/src/librustc_back/target/x86_64_pc_windows_gnu.rs
+++ b/src/librustc_back/target/x86_64_pc_windows_gnu.rs
@@ -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;
Target {
llvm_target: "x86_64-pc-windows-gnu".to_string(),
diff --git a/src/librustc_trans/trans/base.rs b/src/librustc_trans/trans/base.rs
index 61e81d75607cf..a5f2306aac806 100644
--- a/src/librustc_trans/trans/base.rs
+++ b/src/librustc_trans/trans/base.rs
@@ -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>,
diff --git a/src/librustc_trans/trans/cleanup.rs b/src/librustc_trans/trans/cleanup.rs
index b4b0472512e61..5e472e45775d0 100644
--- a/src/librustc_trans/trans/cleanup.rs
+++ b/src/librustc_trans/trans/cleanup.rs
@@ -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();
diff --git a/src/librustc_trans/trans/common.rs b/src/librustc_trans/trans/common.rs
index 9f65050097ded..d0b81b38ab7a7 100644
--- a/src/librustc_trans/trans/common.rs
+++ b/src/librustc_trans/trans/common.rs
@@ -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
diff --git a/src/librustc_trans/trans/context.rs b/src/librustc_trans/trans/context.rs
index 235538f62c245..b7b7b28a42bfb 100644
--- a/src/librustc_trans/trans/context.rs
+++ b/src/librustc_trans/trans/context.rs
@@ -146,6 +146,7 @@ pub struct LocalCrateContext<'tcx> {
eh_personality: RefCell