Skip to content

Commit f886518

Browse files
committed
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.
1 parent 1c5283b commit f886518

File tree

20 files changed

+205
-45
lines changed

20 files changed

+205
-45
lines changed

src/ci/docker/wasm32-unknown/Dockerfile

+6
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ ENV RUST_CONFIGURE_ARGS \
2525
--set build.nodejs=/node-v9.2.0-linux-x64/bin/node \
2626
--set rust.lld
2727

28+
# Some run-make tests have assertions about code size, and enabling debug
29+
# assertions in libstd causes the binary to be much bigger than it would
30+
# otherwise normally be. We already test libstd with debug assertions in lots of
31+
# other contexts as well
32+
ENV NO_DEBUG_ASSERTIONS=1
33+
2834
ENV SCRIPT python2.7 /checkout/x.py test --target $TARGETS \
2935
src/test/run-make \
3036
src/test/ui \

src/libcore/panic.rs

+10
Original file line numberDiff line numberDiff line change
@@ -249,3 +249,13 @@ impl<'a> fmt::Display for Location<'a> {
249249
write!(formatter, "{}:{}:{}", self.file, self.line, self.col)
250250
}
251251
}
252+
253+
/// An internal trait used by libstd to pass data from libstd to `panic_unwind`
254+
/// and other panic runtimes. Not intended to be stabilized any time soon, do
255+
/// not use.
256+
#[unstable(feature = "std_internals", issue = "0")]
257+
#[doc(hidden)]
258+
pub unsafe trait BoxMeUp {
259+
fn box_me_up(&mut self) -> *mut (Any + Send);
260+
fn get(&self) -> &(Any + Send);
261+
}

src/libpanic_abort/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ pub unsafe extern fn __rust_maybe_catch_panic(f: fn(*mut u8),
5353
// now hopefully.
5454
#[no_mangle]
5555
#[rustc_std_internal_symbol]
56-
pub unsafe extern fn __rust_start_panic(_data: usize, _vtable: usize) -> u32 {
56+
pub unsafe extern fn __rust_start_panic(_payload: usize) -> u32 {
5757
abort();
5858

5959
#[cfg(any(unix, target_os = "cloudabi"))]

src/libpanic_unwind/lib.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,15 @@
3030
issue_tracker_base_url = "https://github.com/rust-lang/rust/issues/")]
3131
#![deny(warnings)]
3232

33+
#![feature(allocator_api)]
3334
#![feature(alloc)]
3435
#![feature(core_intrinsics)]
3536
#![feature(lang_items)]
3637
#![feature(libc)]
3738
#![feature(panic_unwind)]
3839
#![feature(raw)]
3940
#![feature(staged_api)]
41+
#![feature(std_internals)]
4042
#![feature(unwind_attributes)]
4143
#![cfg_attr(target_env = "msvc", feature(raw))]
4244

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

53+
use alloc::boxed::Box;
5154
use core::intrinsics;
5255
use core::mem;
5356
use core::raw;
57+
use core::panic::BoxMeUp;
5458

5559
// Rust runtime's startup objects depend on these symbols, so make them public.
5660
#[cfg(all(target_os="windows", target_arch = "x86", target_env="gnu"))]
@@ -114,9 +118,7 @@ pub unsafe extern "C" fn __rust_maybe_catch_panic(f: fn(*mut u8),
114118
#[no_mangle]
115119
#[cfg_attr(stage0, unwind)]
116120
#[cfg_attr(not(stage0), unwind(allowed))]
117-
pub unsafe extern "C" fn __rust_start_panic(data: usize, vtable: usize) -> u32 {
118-
imp::panic(mem::transmute(raw::TraitObject {
119-
data: data as *mut (),
120-
vtable: vtable as *mut (),
121-
}))
121+
pub unsafe extern "C" fn __rust_start_panic(payload: usize) -> u32 {
122+
let payload = payload as *mut &mut BoxMeUp;
123+
imp::panic(Box::from_raw((*payload).box_me_up()))
122124
}

src/libstd/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@
297297
#![feature(rand)]
298298
#![feature(raw)]
299299
#![feature(rustc_attrs)]
300+
#![feature(std_internals)]
300301
#![feature(stdsimd)]
301302
#![feature(shrink_to)]
302303
#![feature(slice_bytes)]

src/libstd/panicking.rs

+67-21
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
//! * Executing a panic up to doing the actual implementation
1818
//! * Shims around "try"
1919
20+
use core::panic::BoxMeUp;
21+
2022
use io::prelude::*;
2123

2224
use any::Any;
@@ -27,7 +29,7 @@ use intrinsics;
2729
use mem;
2830
use ptr;
2931
use raw;
30-
use sys::stdio::Stderr;
32+
use sys::stdio::{Stderr, stderr_prints_nothing};
3133
use sys_common::rwlock::RWLock;
3234
use sys_common::thread_info;
3335
use sys_common::util;
@@ -57,7 +59,7 @@ extern {
5759
vtable_ptr: *mut usize) -> u32;
5860
#[cfg_attr(stage0, unwind)]
5961
#[cfg_attr(not(stage0), unwind(allowed))]
60-
fn __rust_start_panic(data: usize, vtable: usize) -> u32;
62+
fn __rust_start_panic(payload: usize) -> u32;
6163
}
6264

6365
#[derive(Copy, Clone)]
@@ -164,6 +166,12 @@ fn default_hook(info: &PanicInfo) {
164166
#[cfg(feature = "backtrace")]
165167
use sys_common::backtrace;
166168

169+
// Some platforms know that printing to stderr won't ever actually print
170+
// anything, and if that's the case we can skip everything below.
171+
if stderr_prints_nothing() {
172+
return
173+
}
174+
167175
// If this is a double panic, make sure that we print a backtrace
168176
// for this panic. Otherwise only print it if logging is enabled.
169177
#[cfg(feature = "backtrace")]
@@ -213,15 +221,15 @@ fn default_hook(info: &PanicInfo) {
213221

214222
let prev = LOCAL_STDERR.with(|s| s.borrow_mut().take());
215223
match (prev, err.as_mut()) {
216-
(Some(mut stderr), _) => {
217-
write(&mut *stderr);
218-
let mut s = Some(stderr);
219-
LOCAL_STDERR.with(|slot| {
220-
*slot.borrow_mut() = s.take();
221-
});
222-
}
223-
(None, Some(ref mut err)) => { write(err) }
224-
_ => {}
224+
(Some(mut stderr), _) => {
225+
write(&mut *stderr);
226+
let mut s = Some(stderr);
227+
LOCAL_STDERR.with(|slot| {
228+
*slot.borrow_mut() = s.take();
229+
});
230+
}
231+
(None, Some(ref mut err)) => { write(err) }
232+
_ => {}
225233
}
226234
}
227235

@@ -346,7 +354,7 @@ pub fn begin_panic_fmt(msg: &fmt::Arguments,
346354

347355
let mut s = String::new();
348356
let _ = s.write_fmt(*msg);
349-
rust_panic_with_hook(Box::new(s), Some(msg), file_line_col)
357+
rust_panic_with_hook(&mut PanicPayload::new(s), Some(msg), file_line_col)
350358
}
351359

352360
/// This is the entry point of panicking for panic!() and assert!().
@@ -362,7 +370,34 @@ pub fn begin_panic<M: Any + Send>(msg: M, file_line_col: &(&'static str, u32, u3
362370
// be performed in the parent of this thread instead of the thread that's
363371
// panicking.
364372

365-
rust_panic_with_hook(Box::new(msg), None, file_line_col)
373+
rust_panic_with_hook(&mut PanicPayload::new(msg), None, file_line_col)
374+
}
375+
376+
struct PanicPayload<A> {
377+
inner: Option<A>,
378+
}
379+
380+
impl<A: Send + 'static> PanicPayload<A> {
381+
fn new(inner: A) -> PanicPayload<A> {
382+
PanicPayload { inner: Some(inner) }
383+
}
384+
}
385+
386+
unsafe impl<A: Send + 'static> BoxMeUp for PanicPayload<A> {
387+
fn box_me_up(&mut self) -> *mut (Any + Send) {
388+
let data = match self.inner.take() {
389+
Some(a) => Box::new(a) as Box<Any + Send>,
390+
None => Box::new(()),
391+
};
392+
Box::into_raw(data)
393+
}
394+
395+
fn get(&self) -> &(Any + Send) {
396+
match self.inner {
397+
Some(ref a) => a,
398+
None => &(),
399+
}
400+
}
366401
}
367402

368403
/// Executes the primary logic for a panic, including checking for recursive
@@ -371,9 +406,7 @@ pub fn begin_panic<M: Any + Send>(msg: M, file_line_col: &(&'static str, u32, u3
371406
/// This is the entry point or panics from libcore, formatted panics, and
372407
/// `Box<Any>` panics. Here we'll verify that we're not panicking recursively,
373408
/// run panic hooks, and then delegate to the actual implementation of panics.
374-
#[inline(never)]
375-
#[cold]
376-
fn rust_panic_with_hook(payload: Box<Any + Send>,
409+
fn rust_panic_with_hook(payload: &mut BoxMeUp,
377410
message: Option<&fmt::Arguments>,
378411
file_line_col: &(&'static str, u32, u32)) -> ! {
379412
let (file, line, col) = *file_line_col;
@@ -393,7 +426,7 @@ fn rust_panic_with_hook(payload: Box<Any + Send>,
393426

394427
unsafe {
395428
let info = PanicInfo::internal_constructor(
396-
&*payload,
429+
payload.get(),
397430
message,
398431
Location::internal_constructor(file, line, col),
399432
);
@@ -421,16 +454,29 @@ fn rust_panic_with_hook(payload: Box<Any + Send>,
421454
/// Shim around rust_panic. Called by resume_unwind.
422455
pub fn update_count_then_panic(msg: Box<Any + Send>) -> ! {
423456
update_panic_count(1);
424-
rust_panic(msg)
457+
458+
struct RewrapBox(Box<Any + Send>);
459+
460+
unsafe impl BoxMeUp for RewrapBox {
461+
fn box_me_up(&mut self) -> *mut (Any + Send) {
462+
Box::into_raw(mem::replace(&mut self.0, Box::new(())))
463+
}
464+
465+
fn get(&self) -> &(Any + Send) {
466+
&*self.0
467+
}
468+
}
469+
470+
rust_panic(&mut RewrapBox(msg))
425471
}
426472

427473
/// A private no-mangle function on which to slap yer breakpoints.
428474
#[no_mangle]
429475
#[allow(private_no_mangle_fns)] // yes we get it, but we like breakpoints
430-
pub fn rust_panic(msg: Box<Any + Send>) -> ! {
476+
pub fn rust_panic(mut msg: &mut BoxMeUp) -> ! {
431477
let code = unsafe {
432-
let obj = mem::transmute::<_, raw::TraitObject>(msg);
433-
__rust_start_panic(obj.data as usize, obj.vtable as usize)
478+
let obj = &mut msg as *mut &mut BoxMeUp;
479+
__rust_start_panic(obj as usize)
434480
};
435481
rtabort!("failed to initiate panic, error {}", code)
436482
}

src/libstd/sys/cloudabi/stdio.rs

+4
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,7 @@ pub fn is_ebadf(err: &io::Error) -> bool {
7777
}
7878

7979
pub const STDIN_BUF_SIZE: usize = ::sys_common::io::DEFAULT_BUF_SIZE;
80+
81+
pub fn stderr_prints_nothing() -> bool {
82+
false
83+
}

src/libstd/sys/redox/stdio.rs

+4
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,7 @@ pub fn is_ebadf(err: &io::Error) -> bool {
7575
}
7676

7777
pub const STDIN_BUF_SIZE: usize = ::sys_common::io::DEFAULT_BUF_SIZE;
78+
79+
pub fn stderr_prints_nothing() -> bool {
80+
false
81+
}

src/libstd/sys/unix/stdio.rs

+4
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,7 @@ pub fn is_ebadf(err: &io::Error) -> bool {
7575
}
7676

7777
pub const STDIN_BUF_SIZE: usize = ::sys_common::io::DEFAULT_BUF_SIZE;
78+
79+
pub fn stderr_prints_nothing() -> bool {
80+
false
81+
}

src/libstd/sys/wasm/rwlock.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ impl RWLock {
3030
if *mode >= 0 {
3131
*mode += 1;
3232
} else {
33-
panic!("rwlock locked for writing");
33+
rtabort!("rwlock locked for writing");
3434
}
3535
}
3636

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

src/libstd/sys/wasm/stdio.rs

+4
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,7 @@ pub const STDIN_BUF_SIZE: usize = 0;
6969
pub fn is_ebadf(_err: &io::Error) -> bool {
7070
true
7171
}
72+
73+
pub fn stderr_prints_nothing() -> bool {
74+
!cfg!(feature = "wasm_syscall")
75+
}

src/libstd/sys/windows/stdio.rs

+4
Original file line numberDiff line numberDiff line change
@@ -227,3 +227,7 @@ pub fn is_ebadf(err: &io::Error) -> bool {
227227
// idea is that on windows we use a slightly smaller buffer that's
228228
// been seen to be acceptable.
229229
pub const STDIN_BUF_SIZE: usize = 8 * 1024;
230+
231+
pub fn stderr_prints_nothing() -> bool {
232+
false
233+
}

src/libstd/sys_common/backtrace.rs

+6-7
Original file line numberDiff line numberDiff line change
@@ -139,22 +139,21 @@ pub fn __rust_begin_short_backtrace<F, T>(f: F) -> T
139139
/// Controls how the backtrace should be formatted.
140140
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
141141
pub enum PrintFormat {
142-
/// Show all the frames with absolute path for files.
143-
Full = 2,
144142
/// Show only relevant data from the backtrace.
145-
Short = 3,
143+
Short = 2,
144+
/// Show all the frames with absolute path for files.
145+
Full = 3,
146146
}
147147

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

160159
let val = match env::var_os("RUST_BACKTRACE") {

src/libstd/sys_common/mod.rs

+10-4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@
2828
use sync::Once;
2929
use sys;
3030

31+
macro_rules! rtabort {
32+
($($t:tt)*) => (::sys_common::util::abort(format_args!($($t)*)))
33+
}
34+
35+
macro_rules! rtassert {
36+
($e:expr) => (if !$e {
37+
rtabort!(concat!("assertion failed: ", stringify!($e)));
38+
})
39+
}
40+
3141
pub mod at_exit_imp;
3242
#[cfg(feature = "backtrace")]
3343
pub mod backtrace;
@@ -101,10 +111,6 @@ pub fn at_exit<F: FnOnce() + Send + 'static>(f: F) -> Result<(), ()> {
101111
if at_exit_imp::push(Box::new(f)) {Ok(())} else {Err(())}
102112
}
103113

104-
macro_rules! rtabort {
105-
($($t:tt)*) => (::sys_common::util::abort(format_args!($($t)*)))
106-
}
107-
108114
/// One-time runtime cleanup.
109115
pub fn cleanup() {
110116
static CLEANUP: Once = Once::new();

src/libstd/sys_common/thread_local.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ impl StaticKey {
169169
self.key.store(key, Ordering::SeqCst);
170170
}
171171
INIT_LOCK.unlock();
172-
assert!(key != 0);
172+
rtassert!(key != 0);
173173
return key
174174
}
175175

@@ -190,7 +190,7 @@ impl StaticKey {
190190
imp::destroy(key1);
191191
key2
192192
};
193-
assert!(key != 0);
193+
rtassert!(key != 0);
194194
match self.key.compare_and_swap(0, key as usize, Ordering::SeqCst) {
195195
// The CAS succeeded, so we've created the actual key
196196
0 => key as usize,

src/libstd/sys_common/util.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@
1010

1111
use fmt;
1212
use io::prelude::*;
13-
use sys::stdio::Stderr;
13+
use sys::stdio::{Stderr, stderr_prints_nothing};
1414
use thread;
1515

1616
pub fn dumb_print(args: fmt::Arguments) {
17+
if stderr_prints_nothing() {
18+
return
19+
}
1720
let _ = Stderr::new().map(|mut stderr| stderr.write_fmt(args));
1821
}
1922

0 commit comments

Comments
 (0)