From e5abe669831a59c48faa85e88ed8859b6dc63f49 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Mon, 27 Jan 2014 18:03:37 +1100 Subject: [PATCH 1/2] std: reduce the generic code instantiated by fail!(). This splits the vast majority of the code path taken by `fail!()` (`begin_unwind`) into a separate non-generic inline(never) function, so that uses of `fail!()` only monomorphise a small amount of code, reducing code bloat and making very small crates compile faster. --- src/libstd/rt/unwind.rs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/libstd/rt/unwind.rs b/src/libstd/rt/unwind.rs index 6f3aa4c4fd07b..b8b004b1c3b70 100644 --- a/src/libstd/rt/unwind.rs +++ b/src/libstd/rt/unwind.rs @@ -383,16 +383,31 @@ pub fn begin_unwind_raw(msg: *u8, file: *u8, line: uint) -> ! { } /// This is the entry point of unwinding for fail!() and assert!(). -#[inline(never)] #[cold] // this is the slow path, please never inline this +#[inline(never)] #[cold] // avoid code bloat at the call sites as much as possible pub fn begin_unwind(msg: M, file: &'static str, line: uint) -> ! { - // Note that this should be the only allocation performed in this block. + // Note that this should be the only allocation performed in this code path. // Currently this means that fail!() on OOM will invoke this code path, // but then again we're not really ready for failing on OOM anyway. If // we do start doing this, then we should propagate this allocation to // be performed in the parent of this task instead of the task that's // failing. - let msg = ~msg as ~Any; + // see below for why we do the `Any` coercion here. + begin_unwind_inner(~msg, file, line) +} + + +/// The core of the unwinding. +/// +/// This is non-generic to avoid instantiation bloat in other crates +/// (which makes compilation of small crates noticably slower). (Note: +/// we need the `Any` object anyway, we're not just creating it to +/// avoid being generic.) +/// +/// Do this split took the LLVM IR line counts of `fn main() { fail!() +/// }` from ~1900/3700 (-O/no opts) to 180/590. +#[inline(never)] #[cold] // this is the slow path, please never inline this +fn begin_unwind_inner(msg: ~Any, file: &'static str, line: uint) -> ! { let mut task; { let msg_s = match msg.as_ref::<&'static str>() { From b4bb8c0f4ee1234e9d84aa7cb1a9ad691a509212 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Mon, 27 Jan 2014 22:37:55 +1100 Subject: [PATCH 2/2] std: add begin_unwind_fmt that reduces codesize for formatted fail!(). This ends up saving a single `call` instruction in the optimised code, but saves a few hundred lines of non-optimised IR for `fn main() { fail!("foo {}", "bar"); }` (comparing against the minimal generic baseline from the parent commit). --- src/libstd/macros.rs | 14 ++++++++++++-- src/libstd/rt/mod.rs | 2 +- src/libstd/rt/unwind.rs | 12 ++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/libstd/macros.rs b/src/libstd/macros.rs index e7844101c5fcc..4032b63790bf6 100644 --- a/src/libstd/macros.rs +++ b/src/libstd/macros.rs @@ -48,8 +48,18 @@ macro_rules! fail( ::std::rt::begin_unwind($msg, file!(), line!()) ); ($fmt:expr, $($arg:tt)*) => ( - ::std::rt::begin_unwind(format!($fmt, $($arg)*), file!(), line!()) - ) + { + // a closure can't have return type !, so we need a full + // function to pass to format_args!, *and* we need the + // file and line numbers right here; so an inner bare fn + // is our only choice. + #[inline] + fn run_fmt(fmt: &::std::fmt::Arguments) -> ! { + ::std::rt::begin_unwind_fmt(fmt, file!(), line!()) + } + format_args!(run_fmt, $fmt, $($arg)*) + } + ) ) #[macro_export] diff --git a/src/libstd/rt/mod.rs b/src/libstd/rt/mod.rs index 56f6c8f8e6c5b..0e30f3e2efd09 100644 --- a/src/libstd/rt/mod.rs +++ b/src/libstd/rt/mod.rs @@ -69,7 +69,7 @@ use self::task::{Task, BlockedTask}; pub use self::util::default_sched_threads; // Export unwinding facilities used by the failure macros -pub use self::unwind::{begin_unwind, begin_unwind_raw}; +pub use self::unwind::{begin_unwind, begin_unwind_raw, begin_unwind_fmt}; // FIXME: these probably shouldn't be public... #[doc(hidden)] diff --git a/src/libstd/rt/unwind.rs b/src/libstd/rt/unwind.rs index b8b004b1c3b70..25a92148e96a9 100644 --- a/src/libstd/rt/unwind.rs +++ b/src/libstd/rt/unwind.rs @@ -58,6 +58,7 @@ use any::{Any, AnyRefExt}; use c_str::CString; use cast; +use fmt; use kinds::Send; use option::{Some, None, Option}; use prelude::drop; @@ -382,6 +383,17 @@ pub fn begin_unwind_raw(msg: *u8, file: *u8, line: uint) -> ! { begin_unwind(msg, file, line as uint) } +/// The entry point for unwinding with a formatted message. +/// +/// This is designed to reduce the amount of code required at the call +/// site as much as possible (so that `fail!()` has as low an implact +/// on (e.g.) the inlining of other functions as possible), by moving +/// the actual formatting into this shared place. +#[inline(never)] #[cold] +pub fn begin_unwind_fmt(msg: &fmt::Arguments, file: &'static str, line: uint) -> ! { + begin_unwind_inner(~fmt::format(msg), file, line) +} + /// This is the entry point of unwinding for fail!() and assert!(). #[inline(never)] #[cold] // avoid code bloat at the call sites as much as possible pub fn begin_unwind(msg: M, file: &'static str, line: uint) -> ! {