Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

std: Minimize size of panicking on wasm #49488

Merged
merged 4 commits into from
Apr 17, 2018
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
std: Minimize size of panicking on wasm
This commit applies a few code size optimizations for the wasm target to
the standard library, namely around panics. We notably know that in most
configurations it's impossible for us to print anything in
wasm32-unknown-unknown so we can skip larger portions of panicking that
are otherwise simply informative. This allows us to get quite a nice
size reduction.

Finally we can also tweak where the allocation happens for the
`Box<Any>` that we panic with. By only allocating once unwinding starts
we can reduce the size of a panicking wasm module from 44k to 350 bytes.
  • Loading branch information
alexcrichton committed Apr 13, 2018
commit c3a5d6b130e27d7d7587f56581247d5b08c38594
6 changes: 6 additions & 0 deletions src/ci/docker/wasm32-unknown/Dockerfile
Original file line number Diff line number Diff line change
@@ -25,6 +25,12 @@ ENV RUST_CONFIGURE_ARGS \
--set build.nodejs=/node-v9.2.0-linux-x64/bin/node \
--set rust.lld

# Some run-make tests have assertions about code size, and enabling debug
# assertions in libstd causes the binary to be much bigger than it would
# otherwise normally be. We already test libstd with debug assertions in lots of
# other contexts as well
ENV NO_DEBUG_ASSERTIONS=1

ENV SCRIPT python2.7 /checkout/x.py test --target $TARGETS \
src/test/run-make \
src/test/ui \
10 changes: 10 additions & 0 deletions src/libcore/panic.rs
Original file line number Diff line number Diff line change
@@ -251,3 +251,13 @@ impl<'a> fmt::Display for Location<'a> {
write!(formatter, "{}:{}:{}", self.file, self.line, self.col)
}
}

/// An internal trait used by libstd to pass data from libstd to `panic_unwind`
/// and other panic runtimes. Not intended to be stabilized any time soon, do
/// not use.
#[unstable(feature = "std_internals", issue = "0")]
#[doc(hidden)]
pub unsafe trait BoxMeUp {
fn box_me_up(&mut self) -> *mut (Any + Send);
fn get(&self) -> &(Any + Send);
}
2 changes: 1 addition & 1 deletion src/libpanic_abort/lib.rs
Original file line number Diff line number Diff line change
@@ -52,7 +52,7 @@ pub unsafe extern fn __rust_maybe_catch_panic(f: fn(*mut u8),
// now hopefully.
#[no_mangle]
#[rustc_std_internal_symbol]
pub unsafe extern fn __rust_start_panic(_data: usize, _vtable: usize) -> u32 {
pub unsafe extern fn __rust_start_panic(_payload: usize) -> u32 {
abort();

#[cfg(any(unix, target_os = "cloudabi"))]
12 changes: 7 additions & 5 deletions src/libpanic_unwind/lib.rs
Original file line number Diff line number Diff line change
@@ -29,13 +29,15 @@
html_root_url = "https://doc.rust-lang.org/nightly/",
issue_tracker_base_url = "https://github.com/rust-lang/rust/issues/")]

#![feature(allocator_api)]
#![feature(alloc)]
#![feature(core_intrinsics)]
#![feature(lang_items)]
#![feature(libc)]
#![feature(panic_unwind)]
#![feature(raw)]
#![feature(staged_api)]
#![feature(std_internals)]
#![feature(unwind_attributes)]
#![cfg_attr(target_env = "msvc", feature(raw))]

@@ -47,9 +49,11 @@ extern crate libc;
#[cfg(not(any(target_env = "msvc", all(windows, target_arch = "x86_64", target_env = "gnu"))))]
extern crate unwind;

use alloc::boxed::Box;
use core::intrinsics;
use core::mem;
use core::raw;
use core::panic::BoxMeUp;

// Rust runtime's startup objects depend on these symbols, so make them public.
#[cfg(all(target_os="windows", target_arch = "x86", target_env="gnu"))]
@@ -112,9 +116,7 @@ pub unsafe extern "C" fn __rust_maybe_catch_panic(f: fn(*mut u8),
// implementation.
#[no_mangle]
#[unwind(allowed)]
pub unsafe extern "C" fn __rust_start_panic(data: usize, vtable: usize) -> u32 {
imp::panic(mem::transmute(raw::TraitObject {
data: data as *mut (),
vtable: vtable as *mut (),
}))
pub unsafe extern "C" fn __rust_start_panic(payload: usize) -> u32 {
let payload = payload as *mut &mut BoxMeUp;
imp::panic(Box::from_raw((*payload).box_me_up()))
}
1 change: 1 addition & 0 deletions src/libstd/lib.rs
Original file line number Diff line number Diff line change
@@ -292,6 +292,7 @@
#![feature(rand)]
#![feature(raw)]
#![feature(rustc_attrs)]
#![feature(std_internals)]
#![feature(stdsimd)]
#![feature(shrink_to)]
#![feature(slice_bytes)]
88 changes: 67 additions & 21 deletions src/libstd/panicking.rs
Original file line number Diff line number Diff line change
@@ -17,6 +17,8 @@
//! * Executing a panic up to doing the actual implementation
//! * Shims around "try"

use core::panic::BoxMeUp;

use io::prelude::*;

use any::Any;
@@ -27,7 +29,7 @@ use intrinsics;
use mem;
use ptr;
use raw;
use sys::stdio::Stderr;
use sys::stdio::{Stderr, stderr_prints_nothing};
use sys_common::rwlock::RWLock;
use sys_common::thread_info;
use sys_common::util;
@@ -56,7 +58,7 @@ extern {
data_ptr: *mut usize,
vtable_ptr: *mut usize) -> u32;
#[unwind(allowed)]
fn __rust_start_panic(data: usize, vtable: usize) -> u32;
fn __rust_start_panic(payload: usize) -> u32;
}

#[derive(Copy, Clone)]
@@ -163,6 +165,12 @@ fn default_hook(info: &PanicInfo) {
#[cfg(feature = "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
// for this panic. Otherwise only print it if logging is enabled.
#[cfg(feature = "backtrace")]
@@ -212,15 +220,15 @@ fn default_hook(info: &PanicInfo) {

let prev = LOCAL_STDERR.with(|s| s.borrow_mut().take());
match (prev, err.as_mut()) {
(Some(mut stderr), _) => {
write(&mut *stderr);
let mut s = Some(stderr);
LOCAL_STDERR.with(|slot| {
*slot.borrow_mut() = s.take();
});
}
(None, Some(ref mut err)) => { write(err) }
_ => {}
(Some(mut stderr), _) => {
write(&mut *stderr);
let mut s = Some(stderr);
LOCAL_STDERR.with(|slot| {
*slot.borrow_mut() = s.take();
});
}
(None, Some(ref mut err)) => { write(err) }
_ => {}
}
}

@@ -344,7 +352,7 @@ pub fn begin_panic_fmt(msg: &fmt::Arguments,

let mut s = String::new();
let _ = s.write_fmt(*msg);
rust_panic_with_hook(Box::new(s), Some(msg), file_line_col)
rust_panic_with_hook(&mut PanicPayload::new(s), Some(msg), file_line_col)
}

/// This is the entry point of panicking for panic!() and assert!().
@@ -360,7 +368,34 @@ 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
// panicking.

rust_panic_with_hook(Box::new(msg), None, file_line_col)
rust_panic_with_hook(&mut PanicPayload::new(msg), None, file_line_col)
}

struct PanicPayload<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) {
match self.inner {
Some(ref a) => a,
None => &(),
}
}
}

/// Executes the primary logic for a panic, including checking for recursive
@@ -369,9 +404,7 @@ pub fn begin_panic<M: Any + Send>(msg: M, file_line_col: &(&'static str, u32, u3
/// This is the entry point or panics from libcore, formatted panics, and
/// `Box<Any>` panics. Here we'll verify that we're not panicking recursively,
/// run panic hooks, and then delegate to the actual implementation of panics.
#[inline(never)]
#[cold]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing these seems fine, but it might be good to get some perf stats on some benchmark (though I don't have any particular ideas...).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW the comment here is out of date and no longer correct, this function hasn't survived well through the various refactorings. I'll update it.

fn rust_panic_with_hook(payload: Box<Any + Send>,
fn rust_panic_with_hook(payload: &mut BoxMeUp,
message: Option<&fmt::Arguments>,
file_line_col: &(&'static str, u32, u32)) -> ! {
let (file, line, col) = *file_line_col;
@@ -391,7 +424,7 @@ fn rust_panic_with_hook(payload: Box<Any + Send>,

unsafe {
let info = PanicInfo::internal_constructor(
&*payload,
payload.get(),
message,
Location::internal_constructor(file, line, col),
);
@@ -419,16 +452,29 @@ fn rust_panic_with_hook(payload: Box<Any + Send>,
/// Shim around rust_panic. Called by resume_unwind.
pub fn update_count_then_panic(msg: Box<Any + Send>) -> ! {
update_panic_count(1);
rust_panic(msg)

struct RewrapBox(Box<Any + Send>);

unsafe impl BoxMeUp for RewrapBox {
fn box_me_up(&mut self) -> *mut (Any + Send) {
Box::into_raw(mem::replace(&mut self.0, Box::new(())))
}

fn get(&self) -> &(Any + Send) {
&*self.0
}
}

rust_panic(&mut RewrapBox(msg))
}

/// A private no-mangle function on which to slap yer breakpoints.
#[no_mangle]
#[allow(private_no_mangle_fns)] // yes we get it, but we like breakpoints
pub fn rust_panic(msg: Box<Any + Send>) -> ! {
pub fn rust_panic(mut msg: &mut BoxMeUp) -> ! {
let code = unsafe {
let obj = mem::transmute::<_, raw::TraitObject>(msg);
__rust_start_panic(obj.data as usize, obj.vtable as usize)
let obj = &mut msg as *mut &mut BoxMeUp;
__rust_start_panic(obj as usize)
};
rtabort!("failed to initiate panic, error {}", code)
}
4 changes: 4 additions & 0 deletions src/libstd/sys/cloudabi/stdio.rs
Original file line number Diff line number Diff line change
@@ -77,3 +77,7 @@ pub fn is_ebadf(err: &io::Error) -> bool {
}

pub const STDIN_BUF_SIZE: usize = ::sys_common::io::DEFAULT_BUF_SIZE;

pub fn stderr_prints_nothing() -> bool {
false
}
4 changes: 4 additions & 0 deletions src/libstd/sys/redox/stdio.rs
Original file line number Diff line number Diff line change
@@ -75,3 +75,7 @@ pub fn is_ebadf(err: &io::Error) -> bool {
}

pub const STDIN_BUF_SIZE: usize = ::sys_common::io::DEFAULT_BUF_SIZE;

pub fn stderr_prints_nothing() -> bool {
false
}
4 changes: 4 additions & 0 deletions src/libstd/sys/unix/stdio.rs
Original file line number Diff line number Diff line change
@@ -75,3 +75,7 @@ pub fn is_ebadf(err: &io::Error) -> bool {
}

pub const STDIN_BUF_SIZE: usize = ::sys_common::io::DEFAULT_BUF_SIZE;

pub fn stderr_prints_nothing() -> bool {
false
}
4 changes: 2 additions & 2 deletions src/libstd/sys/wasm/rwlock.rs
Original file line number Diff line number Diff line change
@@ -30,7 +30,7 @@ impl RWLock {
if *mode >= 0 {
*mode += 1;
} else {
panic!("rwlock locked for writing");
rtabort!("rwlock locked for writing");
}
}

@@ -51,7 +51,7 @@ impl RWLock {
if *mode == 0 {
*mode = -1;
} else {
panic!("rwlock locked for reading")
rtabort!("rwlock locked for reading")
}
}

4 changes: 4 additions & 0 deletions src/libstd/sys/wasm/stdio.rs
Original file line number Diff line number Diff line change
@@ -69,3 +69,7 @@ pub const STDIN_BUF_SIZE: usize = 0;
pub fn is_ebadf(_err: &io::Error) -> bool {
true
}

pub fn stderr_prints_nothing() -> bool {
!cfg!(feature = "wasm_syscall")
}
4 changes: 4 additions & 0 deletions src/libstd/sys/windows/stdio.rs
Original file line number Diff line number Diff line change
@@ -227,3 +227,7 @@ pub fn is_ebadf(err: &io::Error) -> bool {
// idea is that on windows we use a slightly smaller buffer that's
// been seen to be acceptable.
pub const STDIN_BUF_SIZE: usize = 8 * 1024;

pub fn stderr_prints_nothing() -> bool {
false
}
13 changes: 6 additions & 7 deletions src/libstd/sys_common/backtrace.rs
Original file line number Diff line number Diff line change
@@ -139,22 +139,21 @@ pub fn __rust_begin_short_backtrace<F, T>(f: F) -> T
/// Controls how the backtrace should be formatted.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum PrintFormat {
/// Show all the frames with absolute path for files.
Full = 2,
/// Show only relevant data from the backtrace.
Short = 3,
Short = 2,
/// Show all the frames with absolute path for files.
Full = 3,
}

// For now logging is turned off by default, and this function checks to see
// whether the magical environment variable is present to see if it's turned on.
pub fn log_enabled() -> Option<PrintFormat> {
static ENABLED: atomic::AtomicIsize = atomic::AtomicIsize::new(0);
match ENABLED.load(Ordering::SeqCst) {
0 => {},
0 => {}
1 => return None,
2 => return Some(PrintFormat::Full),
3 => return Some(PrintFormat::Short),
_ => unreachable!(),
2 => return Some(PrintFormat::Short),
_ => return Some(PrintFormat::Full),
}

let val = match env::var_os("RUST_BACKTRACE") {
14 changes: 10 additions & 4 deletions src/libstd/sys_common/mod.rs
Original file line number Diff line number Diff line change
@@ -28,6 +28,16 @@
use sync::Once;
use sys;

macro_rules! rtabort {
($($t:tt)*) => (::sys_common::util::abort(format_args!($($t)*)))
}

macro_rules! rtassert {
($e:expr) => (if !$e {
rtabort!(concat!("assertion failed: ", stringify!($e)));
})
}

pub mod at_exit_imp;
#[cfg(feature = "backtrace")]
pub mod backtrace;
@@ -101,10 +111,6 @@ pub fn at_exit<F: FnOnce() + Send + 'static>(f: F) -> Result<(), ()> {
if at_exit_imp::push(Box::new(f)) {Ok(())} else {Err(())}
}

macro_rules! rtabort {
($($t:tt)*) => (::sys_common::util::abort(format_args!($($t)*)))
}

/// One-time runtime cleanup.
pub fn cleanup() {
static CLEANUP: Once = Once::new();
4 changes: 2 additions & 2 deletions src/libstd/sys_common/thread_local.rs
Original file line number Diff line number Diff line change
@@ -169,7 +169,7 @@ impl StaticKey {
self.key.store(key, Ordering::SeqCst);
}
INIT_LOCK.unlock();
assert!(key != 0);
rtassert!(key != 0);
return key
}

@@ -190,7 +190,7 @@ impl StaticKey {
imp::destroy(key1);
key2
};
assert!(key != 0);
rtassert!(key != 0);
match self.key.compare_and_swap(0, key as usize, Ordering::SeqCst) {
// The CAS succeeded, so we've created the actual key
0 => key as usize,
5 changes: 4 additions & 1 deletion src/libstd/sys_common/util.rs
Original file line number Diff line number Diff line change
@@ -10,10 +10,13 @@

use fmt;
use io::prelude::*;
use sys::stdio::Stderr;
use sys::stdio::{Stderr, stderr_prints_nothing};
use thread;

pub fn dumb_print(args: fmt::Arguments) {
if stderr_prints_nothing() {
return
}
let _ = Stderr::new().map(|mut stderr| stderr.write_fmt(args));
}

Loading