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

@ -250,3 +250,5 @@ pub fn rustc_version() -> String {
option_env!("CFG_VERSION").unwrap_or("unknown version")
)
}
pub const tag_panic_strategy: usize = 0x114;

View file

@ -20,6 +20,7 @@ use loader::{self, CratePaths};
use rustc::hir::svh::Svh;
use rustc::dep_graph::{DepGraph, DepNode};
use rustc::session::{config, Session};
use rustc::session::config::PanicStrategy;
use rustc::session::search_paths::PathKind;
use rustc::middle::cstore::{CrateStore, validate_crate_name, ExternCrate};
use rustc::util::nodemap::FnvHashMap;
@ -630,6 +631,85 @@ impl<'a> CrateReader<'a> {
}
}
fn inject_panic_runtime(&mut self, krate: &ast::Crate) {
// If we're only compiling an rlib, then there's no need to select a
// panic runtime, so we just skip this section entirely.
let any_non_rlib = self.sess.crate_types.borrow().iter().any(|ct| {
*ct != config::CrateTypeRlib
});
if !any_non_rlib {
info!("panic runtime injection skipped, only generating rlib");
return
}
// If we need a panic runtime, we try to find an existing one here. At
// the same time we perform some general validation of the DAG we've got
// going such as ensuring everything has a compatible panic strategy.
//
// The logic for finding the panic runtime here is pretty much the same
// as the allocator case with the only addition that the panic strategy
// compilation mode also comes into play.
let desired_strategy = self.sess.opts.cg.panic.clone();
let mut runtime_found = false;
let mut needs_panic_runtime = attr::contains_name(&krate.attrs,
"needs_panic_runtime");
self.cstore.iter_crate_data(|cnum, data| {
needs_panic_runtime = needs_panic_runtime || data.needs_panic_runtime();
if data.is_panic_runtime() {
// Inject a dependency from all #![needs_panic_runtime] to this
// #![panic_runtime] crate.
self.inject_dependency_if(cnum, "a panic runtime",
&|data| data.needs_panic_runtime());
runtime_found = runtime_found || data.explicitly_linked.get();
}
});
// If an explicitly linked and matching panic runtime was found, or if
// we just don't need one at all, then we're done here and there's
// nothing else to do.
if !needs_panic_runtime || runtime_found {
return
}
// By this point we know that we (a) need a panic runtime and (b) no
// panic runtime was explicitly linked. Here we just load an appropriate
// default runtime for our panic strategy and then inject the
// dependencies.
//
// We may resolve to an already loaded crate (as the crate may not have
// been explicitly linked prior to this) and we may re-inject
// dependencies again, but both of those situations are fine.
//
// Also note that we have yet to perform validation of the crate graph
// in terms of everyone has a compatible panic runtime format, that's
// performed later as part of the `dependency_format` module.
let name = match desired_strategy {
PanicStrategy::Unwind => "panic_unwind",
PanicStrategy::Abort => "panic_abort",
};
info!("panic runtime not found -- loading {}", name);
let (cnum, data, _) = self.resolve_crate(&None, name, name, None,
codemap::DUMMY_SP,
PathKind::Crate, false);
// Sanity check the loaded crate to ensure it is indeed a panic runtime
// and the panic strategy is indeed what we thought it was.
if !data.is_panic_runtime() {
self.sess.err(&format!("the crate `{}` is not a panic runtime",
name));
}
if data.panic_strategy() != desired_strategy {
self.sess.err(&format!("the crate `{}` does not have the panic \
strategy `{}`",
name, desired_strategy.desc()));
}
self.sess.injected_panic_runtime.set(Some(cnum));
self.inject_dependency_if(cnum, "a panic runtime",
&|data| data.needs_panic_runtime());
}
fn inject_allocator_crate(&mut self) {
// Make sure that we actually need an allocator, if none of our
// dependencies need one then we definitely don't!
@ -641,8 +721,9 @@ impl<'a> CrateReader<'a> {
self.cstore.iter_crate_data(|cnum, data| {
needs_allocator = needs_allocator || data.needs_allocator();
if data.is_allocator() {
debug!("{} required by rlib and is an allocator", data.name());
self.inject_allocator_dependency(cnum);
info!("{} required by rlib and is an allocator", data.name());
self.inject_dependency_if(cnum, "an allocator",
&|data| data.needs_allocator());
found_required_allocator = found_required_allocator ||
data.explicitly_linked.get();
}
@ -692,58 +773,68 @@ impl<'a> CrateReader<'a> {
codemap::DUMMY_SP,
PathKind::Crate, false);
// To ensure that the `-Z allocation-crate=foo` option isn't abused, and
// to ensure that the allocator is indeed an allocator, we verify that
// the crate loaded here is indeed tagged #![allocator].
// Sanity check the crate we loaded to ensure that it is indeed an
// allocator.
if !data.is_allocator() {
self.sess.err(&format!("the allocator crate `{}` is not tagged \
with #![allocator]", data.name()));
}
self.sess.injected_allocator.set(Some(cnum));
self.inject_allocator_dependency(cnum);
self.inject_dependency_if(cnum, "an allocator",
&|data| data.needs_allocator());
}
fn inject_allocator_dependency(&self, allocator: ast::CrateNum) {
// Before we inject any dependencies, make sure we don't inject a
// circular dependency by validating that this allocator crate doesn't
// transitively depend on any `#![needs_allocator]` crates.
validate(self, allocator, allocator);
fn inject_dependency_if(&self,
krate: ast::CrateNum,
what: &str,
needs_dep: &Fn(&cstore::crate_metadata) -> bool) {
// don't perform this validation if the session has errors, as one of
// those errors may indicate a circular dependency which could cause
// this to stack overflow.
if self.sess.has_errors() {
return
}
// All crates tagged with `needs_allocator` do not explicitly depend on
// the allocator selected for this compile, but in order for this
// compilation to be successfully linked we need to inject a dependency
// (to order the crates on the command line correctly).
//
// Here we inject a dependency from all crates with #![needs_allocator]
// to the crate tagged with #![allocator] for this compilation unit.
// Before we inject any dependencies, make sure we don't inject a
// circular dependency by validating that this crate doesn't
// transitively depend on any crates satisfying `needs_dep`.
validate(self, krate, krate, what, needs_dep);
// All crates satisfying `needs_dep` do not explicitly depend on the
// crate provided for this compile, but in order for this compilation to
// be successfully linked we need to inject a dependency (to order the
// crates on the command line correctly).
self.cstore.iter_crate_data(|cnum, data| {
if !data.needs_allocator() {
if !needs_dep(data) {
return
}
info!("injecting a dep from {} to {}", cnum, allocator);
info!("injecting a dep from {} to {}", cnum, krate);
let mut cnum_map = data.cnum_map.borrow_mut();
let remote_cnum = cnum_map.len() + 1;
let prev = cnum_map.insert(remote_cnum as ast::CrateNum, allocator);
let prev = cnum_map.insert(remote_cnum as ast::CrateNum, krate);
assert!(prev.is_none());
});
fn validate(me: &CrateReader, krate: ast::CrateNum,
allocator: ast::CrateNum) {
fn validate(me: &CrateReader,
krate: ast::CrateNum,
root: ast::CrateNum,
what: &str,
needs_dep: &Fn(&cstore::crate_metadata) -> bool) {
let data = me.cstore.get_crate_data(krate);
if data.needs_allocator() {
if needs_dep(&data) {
let krate_name = data.name();
let data = me.cstore.get_crate_data(allocator);
let alloc_name = data.name();
me.sess.err(&format!("the allocator crate `{}` cannot depend \
on a crate that needs an allocator, but \
it depends on `{}`", alloc_name,
let data = me.cstore.get_crate_data(root);
let root_name = data.name();
me.sess.err(&format!("the crate `{}` cannot depend \
on a crate that needs {}, but \
it depends on `{}`", root_name, what,
krate_name));
}
for (_, &dep) in data.cnum_map.borrow().iter() {
validate(me, dep, allocator);
validate(me, dep, root, what, needs_dep);
}
}
}
@ -774,6 +865,7 @@ impl<'a> LocalCrateReader<'a> {
self.process_crate(self.krate);
visit::walk_crate(self, self.krate);
self.creader.inject_allocator_crate();
self.creader.inject_panic_runtime(self.krate);
if log_enabled!(log::INFO) {
dump_crates(&self.cstore);

View file

@ -24,6 +24,7 @@ use rustc::hir::map as hir_map;
use rustc::mir::repr::Mir;
use rustc::mir::mir_map::MirMap;
use rustc::util::nodemap::{FnvHashMap, NodeMap, NodeSet, DefIdMap};
use rustc::session::config::PanicStrategy;
use std::cell::RefCell;
use std::rc::Rc;
@ -306,6 +307,15 @@ impl<'tcx> CrateStore<'tcx> for cstore::CStore {
self.get_crate_data(cnum).is_allocator()
}
fn is_panic_runtime(&self, cnum: ast::CrateNum) -> bool
{
self.get_crate_data(cnum).is_panic_runtime()
}
fn panic_strategy(&self, cnum: ast::CrateNum) -> PanicStrategy {
self.get_crate_data(cnum).panic_strategy()
}
fn crate_attrs(&self, cnum: ast::CrateNum) -> Vec<ast::Attribute>
{
decoder::get_crate_attributes(self.get_crate_data(cnum).data())

View file

@ -23,6 +23,7 @@ use loader;
use rustc::hir::def_id::DefId;
use rustc::hir::svh::Svh;
use rustc::middle::cstore::{ExternCrate};
use rustc::session::config::PanicStrategy;
use rustc::util::nodemap::{FnvHashMap, NodeMap, NodeSet, DefIdMap};
use std::cell::{RefCell, Ref, Cell};
@ -281,6 +282,20 @@ impl crate_metadata {
let attrs = decoder::get_crate_attributes(self.data());
attr::contains_name(&attrs, "needs_allocator")
}
pub fn is_panic_runtime(&self) -> bool {
let attrs = decoder::get_crate_attributes(self.data());
attr::contains_name(&attrs, "panic_runtime")
}
pub fn needs_panic_runtime(&self) -> bool {
let attrs = decoder::get_crate_attributes(self.data());
attr::contains_name(&attrs, "needs_panic_runtime")
}
pub fn panic_strategy(&self) -> PanicStrategy {
decoder::get_panic_strategy(self.data())
}
}
impl MetadataBlob {

View file

@ -27,6 +27,7 @@ use rustc::hir::svh::Svh;
use rustc::hir::map as hir_map;
use rustc::util::nodemap::FnvHashMap;
use rustc::hir;
use rustc::session::config::PanicStrategy;
use middle::cstore::{LOCAL_CRATE, FoundAst, InlinedItem, LinkagePreference};
use middle::cstore::{DefLike, DlDef, DlField, DlImpl, tls};
@ -1760,3 +1761,13 @@ pub fn def_path(cdata: Cmd, id: DefIndex) -> hir_map::DefPath {
debug!("def_path(id={:?})", id);
hir_map::DefPath::make(cdata.cnum, id, |parent| def_key(cdata, parent))
}
pub fn get_panic_strategy(data: &[u8]) -> PanicStrategy {
let crate_doc = rbml::Doc::new(data);
let strat_doc = reader::get_doc(crate_doc, tag_panic_strategy);
match reader::doc_as_u8(strat_doc) {
b'U' => PanicStrategy::Unwind,
b'A' => PanicStrategy::Abort,
b => panic!("unknown panic strategy in metadata: {}", b),
}
}

View file

@ -33,7 +33,7 @@ use rustc::ty::util::IntTypeExt;
use rustc::hir::svh::Svh;
use rustc::mir::mir_map::MirMap;
use rustc::session::config;
use rustc::session::config::{self, PanicStrategy};
use rustc::util::nodemap::{FnvHashMap, NodeMap, NodeSet};
use rustc_serialize::Encodable;
@ -1828,6 +1828,17 @@ fn encode_dylib_dependency_formats(rbml_w: &mut Encoder, ecx: &EncodeContext) {
}
}
fn encode_panic_strategy(rbml_w: &mut Encoder, ecx: &EncodeContext) {
match ecx.tcx.sess.opts.cg.panic {
PanicStrategy::Unwind => {
rbml_w.wr_tagged_u8(tag_panic_strategy, b'U');
}
PanicStrategy::Abort => {
rbml_w.wr_tagged_u8(tag_panic_strategy, b'A');
}
}
}
// NB: Increment this as you change the metadata encoding version.
#[allow(non_upper_case_globals)]
pub const metadata_encoding_version : &'static [u8] = &[b'r', b'u', b's', b't', 0, 0, 0, 2 ];
@ -1915,6 +1926,7 @@ fn encode_metadata_inner(rbml_w: &mut Encoder,
encode_hash(rbml_w, &ecx.link_meta.crate_hash);
encode_crate_disambiguator(rbml_w, &ecx.tcx.sess.crate_disambiguator.get().as_str());
encode_dylib_dependency_formats(rbml_w, &ecx);
encode_panic_strategy(rbml_w, &ecx);
let mut i = rbml_w.writer.seek(SeekFrom::Current(0)).unwrap();
encode_attributes(rbml_w, &krate.attrs);