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
|
|
@ -250,3 +250,5 @@ pub fn rustc_version() -> String {
|
|||
option_env!("CFG_VERSION").unwrap_or("unknown version")
|
||||
)
|
||||
}
|
||||
|
||||
pub const tag_panic_strategy: usize = 0x114;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue