about summary refs log tree commit diff
path: root/src/librustc
diff options
context:
space:
mode:
authorAlex Crichton <alex@alexcrichton.com>2016-04-08 16:18:40 -0700
committerAlex Crichton <alex@alexcrichton.com>2016-05-09 08:22:36 -0700
commit0ec321f7b541fcbfbf20286beb497e6d9d3352b2 (patch)
tree30abd6498f7e3ae65fa94057e2bd46f6c769fcf2 /src/librustc
parent32683ce1930ef1390f20e4ab72650e6804fd1c1b (diff)
downloadrust-0ec321f7b541fcbfbf20286beb497e6d9d3352b2.tar.gz
rust-0ec321f7b541fcbfbf20286beb497e6d9d3352b2.zip
rustc: Implement custom panic runtimes
This commit is an implementation of [RFC 1513] which allows applications to
alter the behavior of panics at compile time. A new compiler flag, `-C panic`,
is added and accepts the values `unwind` or `panic`, with the default being
`unwind`. This model affects how code is generated for the local crate, skipping
generation of landing pads with `-C panic=abort`.

[RFC 1513]: https://github.com/rust-lang/rfcs/blob/master/text/1513-less-unwinding.md

Panic implementations are then provided by crates tagged with
`#![panic_runtime]` and lazily required by crates with
`#![needs_panic_runtime]`. The panic strategy (`-C panic` value) of the panic
runtime must match the final product, and if the panic strategy is not `abort`
then the entire DAG must have the same panic strategy.

With the `-C panic=abort` strategy, users can expect a stable method to disable
generation of landing pads, improving optimization in niche scenarios,
decreasing compile time, and decreasing output binary size. With the `-C
panic=unwind` strategy users can expect the existing ability to isolate failure
in Rust code from the outside world.

Organizationally, this commit dismantles the `sys_common::unwind` module in
favor of some bits moving part of it to `libpanic_unwind` and the rest into the
`panicking` module in libstd. The custom panic runtime support is pretty similar
to the custom allocator support with the only major difference being how the
panic runtime is injected (takes the `-C panic` flag into account).
Diffstat (limited to 'src/librustc')
-rw-r--r--src/librustc/middle/cstore.rs7
-rw-r--r--src/librustc/middle/dependency_format.rs137
-rw-r--r--src/librustc/middle/weak_lang_items.rs19
-rw-r--r--src/librustc/session/config.rs30
-rw-r--r--src/librustc/session/mod.rs11
5 files changed, 159 insertions, 45 deletions
diff --git a/src/librustc/middle/cstore.rs b/src/librustc/middle/cstore.rs
index c1a8f747de1..03550565596 100644
--- a/src/librustc/middle/cstore.rs
+++ b/src/librustc/middle/cstore.rs
@@ -31,6 +31,7 @@ use hir::def_id::{DefId, DefIndex};
 use mir::repr::Mir;
 use mir::mir_map::MirMap;
 use session::Session;
+use session::config::PanicStrategy;
 use session::search_paths::PathKind;
 use util::nodemap::{FnvHashMap, NodeMap, NodeSet, DefIdMap};
 use std::any::Any;
@@ -222,6 +223,8 @@ pub trait CrateStore<'tcx> : Any {
     fn is_staged_api(&self, cnum: ast::CrateNum) -> bool;
     fn is_explicitly_linked(&self, cnum: ast::CrateNum) -> bool;
     fn is_allocator(&self, cnum: ast::CrateNum) -> bool;
+    fn is_panic_runtime(&self, cnum: ast::CrateNum) -> bool;
+    fn panic_strategy(&self, cnum: ast::CrateNum) -> PanicStrategy;
     fn extern_crate(&self, cnum: ast::CrateNum) -> Option<ExternCrate>;
     fn crate_attrs(&self, cnum: ast::CrateNum) -> Vec<ast::Attribute>;
     /// The name of the crate as it is referred to in source code of the current
@@ -408,6 +411,10 @@ impl<'tcx> CrateStore<'tcx> for DummyCrateStore {
     fn is_staged_api(&self, cnum: ast::CrateNum) -> bool { bug!("is_staged_api") }
     fn is_explicitly_linked(&self, cnum: ast::CrateNum) -> bool { bug!("is_explicitly_linked") }
     fn is_allocator(&self, cnum: ast::CrateNum) -> bool { bug!("is_allocator") }
+    fn is_panic_runtime(&self, cnum: ast::CrateNum) -> bool { bug!("is_panic_runtime") }
+    fn panic_strategy(&self, cnum: ast::CrateNum) -> PanicStrategy {
+        bug!("panic_strategy")
+    }
     fn extern_crate(&self, cnum: ast::CrateNum) -> Option<ExternCrate> { bug!("extern_crate") }
     fn crate_attrs(&self, cnum: ast::CrateNum) -> Vec<ast::Attribute>
         { bug!("crate_attrs") }
diff --git a/src/librustc/middle/dependency_format.rs b/src/librustc/middle/dependency_format.rs
index c2498b7b5ee..fe22cfdb43f 100644
--- a/src/librustc/middle/dependency_format.rs
+++ b/src/librustc/middle/dependency_format.rs
@@ -64,7 +64,7 @@
 use syntax::ast;
 
 use session;
-use session::config;
+use session::config::{self, PanicStrategy};
 use middle::cstore::LinkagePreference::{self, RequireStatic, RequireDynamic};
 use util::nodemap::FnvHashMap;
 
@@ -193,10 +193,15 @@ fn calculate_type(sess: &session::Session,
     }
 
     // We've gotten this far because we're emitting some form of a final
-    // artifact which means that we're going to need an allocator of some form.
-    // No allocator may have been required or linked so far, so activate one
-    // here if one isn't set.
-    activate_allocator(sess, &mut ret);
+    // artifact which means that we may need to inject dependencies of some
+    // form.
+    //
+    // Things like allocators and panic runtimes may not have been activated
+    // quite yet, so do so here.
+    activate_injected_dep(sess.injected_allocator.get(), &mut ret,
+                          &|cnum| sess.cstore.is_allocator(cnum));
+    activate_injected_dep(sess.injected_panic_runtime.get(), &mut ret,
+                          &|cnum| sess.cstore.is_panic_runtime(cnum));
 
     // When dylib B links to dylib A, then when using B we must also link to A.
     // It could be the case, however, that the rlib for A is present (hence we
@@ -270,40 +275,42 @@ fn attempt_static(sess: &session::Session) -> Option<DependencyList> {
         }
     }).collect::<Vec<_>>();
 
-    // Our allocator may not have been activated as it's not flagged with
-    // explicitly_linked, so flag it here if necessary.
-    activate_allocator(sess, &mut ret);
+    // Our allocator/panic runtime may not have been linked above if it wasn't
+    // explicitly linked, which is the case for any injected dependency. Handle
+    // that here and activate them.
+    activate_injected_dep(sess.injected_allocator.get(), &mut ret,
+                          &|cnum| sess.cstore.is_allocator(cnum));
+    activate_injected_dep(sess.injected_panic_runtime.get(), &mut ret,
+                          &|cnum| sess.cstore.is_panic_runtime(cnum));
 
     Some(ret)
 }
 
 // Given a list of how to link upstream dependencies so far, ensure that an
-// allocator is activated. This will not do anything if one was transitively
-// included already (e.g. via a dylib or explicitly so).
+// injected dependency is activated. This will not do anything if one was
+// transitively included already (e.g. via a dylib or explicitly so).
 //
-// If an allocator was not found then we're guaranteed the metadata::creader
-// module has injected an allocator dependency (not listed as a required
-// dependency) in the session's `injected_allocator` field. If this field is not
-// set then this compilation doesn't actually need an allocator and we can also
-// skip this step entirely.
-fn activate_allocator(sess: &session::Session, list: &mut DependencyList) {
-    let mut allocator_found = false;
+// If an injected dependency was not found then we're guaranteed the
+// metadata::creader module has injected that dependency (not listed as
+// a required dependency) in one of the session's field. If this field is not
+// set then this compilation doesn't actually need the dependency and we can
+// also skip this step entirely.
+fn activate_injected_dep(injected: Option<ast::CrateNum>,
+                         list: &mut DependencyList,
+                         replaces_injected: &Fn(ast::CrateNum) -> bool) {
     for (i, slot) in list.iter().enumerate() {
         let cnum = (i + 1) as ast::CrateNum;
-        if !sess.cstore.is_allocator(cnum) {
+        if !replaces_injected(cnum) {
             continue
         }
-        if let Linkage::NotLinked = *slot {
-            continue
+        if *slot != Linkage::NotLinked {
+            return
         }
-        allocator_found = true;
     }
-    if !allocator_found {
-        if let Some(injected_allocator) = sess.injected_allocator.get() {
-            let idx = injected_allocator as usize - 1;
-            assert_eq!(list[idx], Linkage::NotLinked);
-            list[idx] = Linkage::Static;
-        }
+    if let Some(injected) = injected {
+        let idx = injected as usize - 1;
+        assert_eq!(list[idx], Linkage::NotLinked);
+        list[idx] = Linkage::Static;
     }
 }
 
@@ -314,21 +321,75 @@ fn verify_ok(sess: &session::Session, list: &[Linkage]) {
         return
     }
     let mut allocator = None;
+    let mut panic_runtime = None;
     for (i, linkage) in list.iter().enumerate() {
-        let cnum = (i + 1) as ast::CrateNum;
-        if !sess.cstore.is_allocator(cnum) {
-            continue
-        }
         if let Linkage::NotLinked = *linkage {
             continue
         }
-        if let Some(prev_alloc) = allocator {
-            let prev_name = sess.cstore.crate_name(prev_alloc);
-            let cur_name = sess.cstore.crate_name(cnum);
-            sess.err(&format!("cannot link together two \
-                               allocators: {} and {}",
-                              prev_name, cur_name));
+        let cnum = (i + 1) as ast::CrateNum;
+        if sess.cstore.is_allocator(cnum) {
+            if let Some(prev) = allocator {
+                let prev_name = sess.cstore.crate_name(prev);
+                let cur_name = sess.cstore.crate_name(cnum);
+                sess.err(&format!("cannot link together two \
+                                   allocators: {} and {}",
+                                  prev_name, cur_name));
+            }
+            allocator = Some(cnum);
+        }
+
+        if sess.cstore.is_panic_runtime(cnum) {
+            if let Some((prev, _)) = panic_runtime {
+                let prev_name = sess.cstore.crate_name(prev);
+                let cur_name = sess.cstore.crate_name(cnum);
+                sess.err(&format!("cannot link together two \
+                                   panic runtimes: {} and {}",
+                                  prev_name, cur_name));
+            }
+            panic_runtime = Some((cnum, sess.cstore.panic_strategy(cnum)));
+        }
+    }
+
+    // If we found a panic runtime, then we know by this point that it's the
+    // only one, but we perform validation here that all the panic strategy
+    // compilation modes for the whole DAG are valid.
+    if let Some((cnum, found_strategy)) = panic_runtime {
+        let desired_strategy = sess.opts.cg.panic.clone();
+
+        // First up, validate that our selected panic runtime is indeed exactly
+        // our same strategy.
+        if found_strategy != desired_strategy {
+            sess.err(&format!("the linked panic runtime `{}` is \
+                               not compiled with this crate's \
+                               panic strategy `{}`",
+                              sess.cstore.crate_name(cnum),
+                              desired_strategy.desc()));
+        }
+
+        // Next up, verify that all other crates are compatible with this panic
+        // strategy. If the dep isn't linked, we ignore it, and if our strategy
+        // is abort then it's compatible with everything. Otherwise all crates'
+        // panic strategy must match our own.
+        for (i, linkage) in list.iter().enumerate() {
+            if let Linkage::NotLinked = *linkage {
+                continue
+            }
+            if desired_strategy == PanicStrategy::Abort {
+                continue
+            }
+            let cnum = (i + 1) as ast::CrateNum;
+            let found_strategy = sess.cstore.panic_strategy(cnum);
+            if desired_strategy == found_strategy {
+                continue
+            }
+
+            sess.err(&format!("the crate `{}` is compiled with the \
+                               panic strategy `{}` which is \
+                               incompatible with this crate's \
+                               strategy of `{}`",
+                              sess.cstore.crate_name(cnum),
+                              found_strategy.desc(),
+                              desired_strategy.desc()));
         }
-        allocator = Some(cnum);
     }
 }
diff --git a/src/librustc/middle/weak_lang_items.rs b/src/librustc/middle/weak_lang_items.rs
index 5fe6076e538..b7dfc867204 100644
--- a/src/librustc/middle/weak_lang_items.rs
+++ b/src/librustc/middle/weak_lang_items.rs
@@ -10,7 +10,7 @@
 
 //! Validity checking for weak lang items
 
-use session::config;
+use session::config::{self, PanicStrategy};
 use session::Session;
 use middle::lang_items;
 
@@ -75,7 +75,9 @@ fn verify(sess: &Session, items: &lang_items::LanguageItems) {
             config::CrateTypeRlib => false,
         }
     });
-    if !needs_check { return }
+    if !needs_check {
+        return
+    }
 
     let mut missing = HashSet::new();
     for cnum in sess.cstore.crates() {
@@ -84,8 +86,19 @@ fn verify(sess: &Session, items: &lang_items::LanguageItems) {
         }
     }
 
+    // If we're not compiling with unwinding, we won't actually need these
+    // symbols. Other panic runtimes ensure that the relevant symbols are
+    // available to link things together, but they're never exercised.
+    let mut whitelisted = HashSet::new();
+    if sess.opts.cg.panic != PanicStrategy::Unwind {
+        whitelisted.insert(lang_items::EhPersonalityLangItem);
+        whitelisted.insert(lang_items::EhUnwindResumeLangItem);
+    }
+
     $(
-        if missing.contains(&lang_items::$item) && items.$name().is_none() {
+        if missing.contains(&lang_items::$item) &&
+           !whitelisted.contains(&lang_items::$item) &&
+           items.$name().is_none() {
             sess.err(&format!("language item required, but not found: `{}`",
                               stringify!($name)));
 
diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs
index 1a2c1b9a095..82e5ce07b13 100644
--- a/src/librustc/session/config.rs
+++ b/src/librustc/session/config.rs
@@ -317,6 +317,21 @@ impl Passes {
     }
 }
 
+#[derive(Clone, PartialEq)]
+pub enum PanicStrategy {
+    Unwind,
+    Abort,
+}
+
+impl PanicStrategy {
+    pub fn desc(&self) -> &str {
+        match *self {
+            PanicStrategy::Unwind => "unwind",
+            PanicStrategy::Abort => "abort",
+        }
+    }
+}
+
 /// Declare a macro that will define all CodegenOptions/DebuggingOptions fields and parsers all
 /// at once. The goal of this macro is to define an interface that can be
 /// programmatically used by the option parser in order to initialize the struct
@@ -402,11 +417,13 @@ macro_rules! options {
             Some("a space-separated list of passes, or `all`");
         pub const parse_opt_uint: Option<&'static str> =
             Some("a number");
+        pub const parse_panic_strategy: Option<&'static str> =
+            Some("either `panic` or `abort`");
     }
 
     #[allow(dead_code)]
     mod $mod_set {
-        use super::{$struct_name, Passes, SomePasses, AllPasses};
+        use super::{$struct_name, Passes, SomePasses, AllPasses, PanicStrategy};
 
         $(
             pub fn $opt(cg: &mut $struct_name, v: Option<&str>) -> bool {
@@ -510,6 +527,15 @@ macro_rules! options {
                 }
             }
         }
+
+        fn parse_panic_strategy(slot: &mut PanicStrategy, v: Option<&str>) -> bool {
+            match v {
+                Some("unwind") => *slot = PanicStrategy::Unwind,
+                Some("abort") => *slot = PanicStrategy::Abort,
+                _ => return false
+            }
+            true
+        }
     }
 ) }
 
@@ -575,6 +601,8 @@ options! {CodegenOptions, CodegenSetter, basic_codegen_options,
         "explicitly enable the cfg(debug_assertions) directive"),
     inline_threshold: Option<usize> = (None, parse_opt_uint,
         "set the inlining threshold for"),
+    panic: PanicStrategy = (PanicStrategy::Unwind, parse_panic_strategy,
+        "panic strategy to compile crate with"),
 }
 
 
diff --git a/src/librustc/session/mod.rs b/src/librustc/session/mod.rs
index edb1c4530c2..1bea01c4849 100644
--- a/src/librustc/session/mod.rs
+++ b/src/librustc/session/mod.rs
@@ -12,6 +12,7 @@ use lint;
 use middle::cstore::CrateStore;
 use middle::dependency_format;
 use session::search_paths::PathKind;
+use session::config::PanicStrategy;
 use ty::tls;
 use util::nodemap::{NodeMap, FnvHashMap};
 use mir::transform as mir_pass;
@@ -82,9 +83,11 @@ pub struct Session {
     /// operations such as auto-dereference and monomorphization.
     pub recursion_limit: Cell<usize>,
 
-    /// The metadata::creader module may inject an allocator dependency if it
-    /// didn't already find one, and this tracks what was injected.
+    /// The metadata::creader module may inject an allocator/panic_runtime
+    /// dependency if it didn't already find one, and this tracks what was
+    /// injected.
     pub injected_allocator: Cell<Option<ast::CrateNum>>,
+    pub injected_panic_runtime: Cell<Option<ast::CrateNum>>,
 
     /// Names of all bang-style macros and syntax extensions
     /// available in this crate
@@ -295,7 +298,8 @@ impl Session {
         self.opts.cg.lto
     }
     pub fn no_landing_pads(&self) -> bool {
-        self.opts.debugging_opts.no_landing_pads
+        self.opts.debugging_opts.no_landing_pads ||
+            self.opts.cg.panic == PanicStrategy::Abort
     }
     pub fn unstable_options(&self) -> bool {
         self.opts.debugging_opts.unstable_options
@@ -502,6 +506,7 @@ pub fn build_session_(sopts: config::Options,
         recursion_limit: Cell::new(64),
         next_node_id: Cell::new(1),
         injected_allocator: Cell::new(None),
+        injected_panic_runtime: Cell::new(None),
         available_macros: RefCell::new(HashSet::new()),
         imported_macro_spans: RefCell::new(HashMap::new()),
     };