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:
parent
32683ce193
commit
0ec321f7b5
76 changed files with 2000 additions and 745 deletions
|
|
@ -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") }
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue