diff options
| author | Alex Crichton <alex@alexcrichton.com> | 2016-04-08 16:18:40 -0700 |
|---|---|---|
| committer | Alex Crichton <alex@alexcrichton.com> | 2016-05-09 08:22:36 -0700 |
| commit | 0ec321f7b541fcbfbf20286beb497e6d9d3352b2 (patch) | |
| tree | 30abd6498f7e3ae65fa94057e2bd46f6c769fcf2 /src/librustc | |
| parent | 32683ce1930ef1390f20e4ab72650e6804fd1c1b (diff) | |
| download | rust-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.rs | 7 | ||||
| -rw-r--r-- | src/librustc/middle/dependency_format.rs | 137 | ||||
| -rw-r--r-- | src/librustc/middle/weak_lang_items.rs | 19 | ||||
| -rw-r--r-- | src/librustc/session/config.rs | 30 | ||||
| -rw-r--r-- | src/librustc/session/mod.rs | 11 |
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()), }; |
