std: Avoid allocating panic message unless needed

This commit removes allocation of the panic message in instances like
`panic!("foo: {}", "bar")` if we don't actually end up needing the message. We
don't need it in the case of wasm32 right now, and in general it's not needed
for panic=abort instances that use the default panic hook.

For now this commit only solves the wasm use case where with LTO the allocation
is entirely removed, but the panic=abort use case can be implemented at a later
date if needed.
This commit is contained in:
Alex Crichton
2018-04-06 14:22:13 -07:00
parent 2bb5b5c07c
commit 46d16b66e0
4 changed files with 105 additions and 52 deletions

View File

@@ -49,11 +49,17 @@ impl<'a> PanicInfo<'a> {
and related macros", and related macros",
issue = "0")] issue = "0")]
#[doc(hidden)] #[doc(hidden)]
pub fn internal_constructor(payload: &'a (Any + Send), #[inline]
message: Option<&'a fmt::Arguments<'a>>, pub fn internal_constructor(message: Option<&'a fmt::Arguments<'a>>,
location: Location<'a>) location: Location<'a>)
-> Self { -> Self {
PanicInfo { payload, location, message } PanicInfo { payload: &(), location, message }
}
#[doc(hidden)]
#[inline]
pub fn set_payload(&mut self, info: &'a (Any + Send)) {
self.payload = info;
} }
/// Returns the payload associated with the panic. /// Returns the payload associated with the panic.
@@ -259,5 +265,5 @@ impl<'a> fmt::Display for Location<'a> {
#[doc(hidden)] #[doc(hidden)]
pub unsafe trait BoxMeUp { pub unsafe trait BoxMeUp {
fn box_me_up(&mut self) -> *mut (Any + Send); fn box_me_up(&mut self) -> *mut (Any + Send);
fn get(&self) -> &(Any + Send); fn get(&mut self) -> &(Any + Send);
} }

View File

@@ -165,12 +165,6 @@ fn default_hook(info: &PanicInfo) {
#[cfg(feature = "backtrace")] #[cfg(feature = "backtrace")]
use sys_common::backtrace; use sys_common::backtrace;
// Some platforms know that printing to stderr won't ever actually print
// anything, and if that's the case we can skip everything below.
if stderr_prints_nothing() {
return
}
// If this is a double panic, make sure that we print a backtrace // If this is a double panic, make sure that we print a backtrace
// for this panic. Otherwise only print it if logging is enabled. // for this panic. Otherwise only print it if logging is enabled.
#[cfg(feature = "backtrace")] #[cfg(feature = "backtrace")]
@@ -185,9 +179,6 @@ fn default_hook(info: &PanicInfo) {
}; };
let location = info.location().unwrap(); // The current implementation always returns Some let location = info.location().unwrap(); // The current implementation always returns Some
let file = location.file();
let line = location.line();
let col = location.column();
let msg = match info.payload().downcast_ref::<&'static str>() { let msg = match info.payload().downcast_ref::<&'static str>() {
Some(s) => *s, Some(s) => *s,
@@ -201,8 +192,8 @@ fn default_hook(info: &PanicInfo) {
let name = thread.as_ref().and_then(|t| t.name()).unwrap_or("<unnamed>"); let name = thread.as_ref().and_then(|t| t.name()).unwrap_or("<unnamed>");
let write = |err: &mut ::io::Write| { let write = |err: &mut ::io::Write| {
let _ = writeln!(err, "thread '{}' panicked at '{}', {}:{}:{}", let _ = writeln!(err, "thread '{}' panicked at '{}', {}",
name, msg, file, line, col); name, msg, location);
#[cfg(feature = "backtrace")] #[cfg(feature = "backtrace")]
{ {
@@ -350,9 +341,38 @@ pub fn begin_panic_fmt(msg: &fmt::Arguments,
// panic + OOM properly anyway (see comment in begin_panic // panic + OOM properly anyway (see comment in begin_panic
// below). // below).
let mut s = String::new(); rust_panic_with_hook(&mut PanicPayload::new(msg), Some(msg), file_line_col);
let _ = s.write_fmt(*msg);
rust_panic_with_hook(&mut PanicPayload::new(s), Some(msg), file_line_col) struct PanicPayload<'a> {
inner: &'a fmt::Arguments<'a>,
string: Option<String>,
}
impl<'a> PanicPayload<'a> {
fn new(inner: &'a fmt::Arguments<'a>) -> PanicPayload<'a> {
PanicPayload { inner, string: None }
}
fn fill(&mut self) -> &mut String {
let inner = self.inner;
self.string.get_or_insert_with(|| {
let mut s = String::new();
drop(s.write_fmt(*inner));
s
})
}
}
unsafe impl<'a> BoxMeUp for PanicPayload<'a> {
fn box_me_up(&mut self) -> *mut (Any + Send) {
let contents = mem::replace(self.fill(), String::new());
Box::into_raw(Box::new(contents))
}
fn get(&mut self) -> &(Any + Send) {
self.fill()
}
}
} }
/// This is the entry point of panicking for panic!() and assert!(). /// This is the entry point of panicking for panic!() and assert!().
@@ -368,42 +388,41 @@ pub fn begin_panic<M: Any + Send>(msg: M, file_line_col: &(&'static str, u32, u3
// be performed in the parent of this thread instead of the thread that's // be performed in the parent of this thread instead of the thread that's
// panicking. // panicking.
rust_panic_with_hook(&mut PanicPayload::new(msg), None, file_line_col) rust_panic_with_hook(&mut PanicPayload::new(msg), None, file_line_col);
}
struct PanicPayload<A> { struct PanicPayload<A> {
inner: Option<A>, inner: Option<A>,
}
impl<A: Send + 'static> PanicPayload<A> {
fn new(inner: A) -> PanicPayload<A> {
PanicPayload { inner: Some(inner) }
}
}
unsafe impl<A: Send + 'static> BoxMeUp for PanicPayload<A> {
fn box_me_up(&mut self) -> *mut (Any + Send) {
let data = match self.inner.take() {
Some(a) => Box::new(a) as Box<Any + Send>,
None => Box::new(()),
};
Box::into_raw(data)
} }
fn get(&self) -> &(Any + Send) { impl<A: Send + 'static> PanicPayload<A> {
match self.inner { fn new(inner: A) -> PanicPayload<A> {
Some(ref a) => a, PanicPayload { inner: Some(inner) }
None => &(), }
}
unsafe impl<A: Send + 'static> BoxMeUp for PanicPayload<A> {
fn box_me_up(&mut self) -> *mut (Any + Send) {
let data = match self.inner.take() {
Some(a) => Box::new(a) as Box<Any + Send>,
None => Box::new(()),
};
Box::into_raw(data)
}
fn get(&mut self) -> &(Any + Send) {
match self.inner {
Some(ref a) => a,
None => &(),
}
} }
} }
} }
/// Executes the primary logic for a panic, including checking for recursive /// Central point for dispatching panics.
/// panics and panic hooks.
/// ///
/// This is the entry point or panics from libcore, formatted panics, and /// Executes the primary logic for a panic, including checking for recursive
/// `Box<Any>` panics. Here we'll verify that we're not panicking recursively, /// panics, panic hooks, and finally dispatching to the panic runtime to either
/// run panic hooks, and then delegate to the actual implementation of panics. /// abort or unwind.
fn rust_panic_with_hook(payload: &mut BoxMeUp, fn rust_panic_with_hook(payload: &mut BoxMeUp,
message: Option<&fmt::Arguments>, message: Option<&fmt::Arguments>,
file_line_col: &(&'static str, u32, u32)) -> ! { file_line_col: &(&'static str, u32, u32)) -> ! {
@@ -423,15 +442,24 @@ fn rust_panic_with_hook(payload: &mut BoxMeUp,
} }
unsafe { unsafe {
let info = PanicInfo::internal_constructor( let mut info = PanicInfo::internal_constructor(
payload.get(),
message, message,
Location::internal_constructor(file, line, col), Location::internal_constructor(file, line, col),
); );
HOOK_LOCK.read(); HOOK_LOCK.read();
match HOOK { match HOOK {
Hook::Default => default_hook(&info), // Some platforms know that printing to stderr won't ever actually
Hook::Custom(ptr) => (*ptr)(&info), // print anything, and if that's the case we can skip the default
// hook.
Hook::Default if stderr_prints_nothing() => {}
Hook::Default => {
info.set_payload(payload.get());
default_hook(&info);
}
Hook::Custom(ptr) => {
info.set_payload(payload.get());
(*ptr)(&info);
}
} }
HOOK_LOCK.read_unlock(); HOOK_LOCK.read_unlock();
} }
@@ -460,7 +488,7 @@ pub fn update_count_then_panic(msg: Box<Any + Send>) -> ! {
Box::into_raw(mem::replace(&mut self.0, Box::new(()))) Box::into_raw(mem::replace(&mut self.0, Box::new(())))
} }
fn get(&self) -> &(Any + Send) { fn get(&mut self) -> &(Any + Send) {
&*self.0 &*self.0
} }
} }

View File

@@ -2,9 +2,15 @@
ifeq ($(TARGET),wasm32-unknown-unknown) ifeq ($(TARGET),wasm32-unknown-unknown)
all: all:
$(RUSTC) foo.rs -C lto -O --target wasm32-unknown-unknown $(RUSTC) foo.rs -C lto -O --target wasm32-unknown-unknown --cfg a
wc -c < $(TMPDIR)/foo.wasm wc -c < $(TMPDIR)/foo.wasm
[ "`wc -c < $(TMPDIR)/foo.wasm`" -lt "1024" ] [ "`wc -c < $(TMPDIR)/foo.wasm`" -lt "1024" ]
$(RUSTC) foo.rs -C lto -O --target wasm32-unknown-unknown --cfg b
wc -c < $(TMPDIR)/foo.wasm
[ "`wc -c < $(TMPDIR)/foo.wasm`" -lt "5120" ]
$(RUSTC) foo.rs -C lto -O --target wasm32-unknown-unknown --cfg c
wc -c < $(TMPDIR)/foo.wasm
[ "`wc -c < $(TMPDIR)/foo.wasm`" -lt "5120" ]
else else
all: all:
endif endif

View File

@@ -11,6 +11,19 @@
#![crate_type = "cdylib"] #![crate_type = "cdylib"]
#[no_mangle] #[no_mangle]
#[cfg(a)]
pub fn foo() { pub fn foo() {
panic!("test"); panic!("test");
} }
#[no_mangle]
#[cfg(b)]
pub fn foo() {
panic!("{}", 1);
}
#[no_mangle]
#[cfg(c)]
pub fn foo() {
panic!("{}", "a");
}