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).
This commit is contained in:
Alex Crichton 2016-04-08 16:18:40 -07:00
parent 32683ce193
commit 0ec321f7b5
76 changed files with 2000 additions and 745 deletions

View file

@ -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") }

View file

@ -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);
}
}

View file

@ -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)));

View file

@ -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"),
}

View file

@ -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()),
};