Skip to content

Commit 3809bbf

Browse files
committed
Auto merge of #49488 - alexcrichton:small-wasm-panic, r=sfackler
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.
2 parents 4a3ab8b + 46d16b6 commit 3809bbf

File tree

22 files changed

+296
-72
lines changed

22 files changed

+296
-72
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/liballoc/raw_vec.rs

+15-8
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,8 @@ impl<T, A: Alloc> RawVec<T, A> {
8686
unsafe {
8787
let elem_size = mem::size_of::<T>();
8888

89-
let alloc_size = cap.checked_mul(elem_size).expect("capacity overflow");
90-
alloc_guard(alloc_size).expect("capacity overflow");
89+
let alloc_size = cap.checked_mul(elem_size).unwrap_or_else(|| capacity_overflow());
90+
alloc_guard(alloc_size).unwrap_or_else(|_| capacity_overflow());
9191

9292
// handles ZSTs and `cap = 0` alike
9393
let ptr = if alloc_size == 0 {
@@ -310,7 +310,7 @@ impl<T, A: Alloc> RawVec<T, A> {
310310
// `from_size_align_unchecked`.
311311
let new_cap = 2 * self.cap;
312312
let new_size = new_cap * elem_size;
313-
alloc_guard(new_size).expect("capacity overflow");
313+
alloc_guard(new_size).unwrap_or_else(|_| capacity_overflow());
314314
let ptr_res = self.a.realloc(NonNull::from(self.ptr).as_opaque(),
315315
cur,
316316
new_size);
@@ -369,7 +369,7 @@ impl<T, A: Alloc> RawVec<T, A> {
369369
// overflow and the alignment is sufficiently small.
370370
let new_cap = 2 * self.cap;
371371
let new_size = new_cap * elem_size;
372-
alloc_guard(new_size).expect("capacity overflow");
372+
alloc_guard(new_size).unwrap_or_else(|_| capacity_overflow());
373373
match self.a.grow_in_place(NonNull::from(self.ptr).as_opaque(), old_layout, new_size) {
374374
Ok(_) => {
375375
// We can't directly divide `size`.
@@ -441,7 +441,7 @@ impl<T, A: Alloc> RawVec<T, A> {
441441

442442
pub fn reserve_exact(&mut self, used_cap: usize, needed_extra_cap: usize) {
443443
match self.try_reserve_exact(used_cap, needed_extra_cap) {
444-
Err(CapacityOverflow) => panic!("capacity overflow"),
444+
Err(CapacityOverflow) => capacity_overflow(),
445445
Err(AllocErr) => self.a.oom(),
446446
Ok(()) => { /* yay */ }
447447
}
@@ -551,7 +551,7 @@ impl<T, A: Alloc> RawVec<T, A> {
551551
/// The same as try_reserve, but errors are lowered to a call to oom().
552552
pub fn reserve(&mut self, used_cap: usize, needed_extra_cap: usize) {
553553
match self.try_reserve(used_cap, needed_extra_cap) {
554-
Err(CapacityOverflow) => panic!("capacity overflow"),
554+
Err(CapacityOverflow) => capacity_overflow(),
555555
Err(AllocErr) => self.a.oom(),
556556
Ok(()) => { /* yay */ }
557557
}
@@ -592,15 +592,15 @@ impl<T, A: Alloc> RawVec<T, A> {
592592
}
593593

594594
let new_cap = self.amortized_new_size(used_cap, needed_extra_cap)
595-
.expect("capacity overflow");
595+
.unwrap_or_else(|_| capacity_overflow());
596596

597597
// Here, `cap < used_cap + needed_extra_cap <= new_cap`
598598
// (regardless of whether `self.cap - used_cap` wrapped).
599599
// Therefore we can safely call grow_in_place.
600600

601601
let new_layout = Layout::new::<T>().repeat(new_cap).unwrap().0;
602602
// FIXME: may crash and burn on over-reserve
603-
alloc_guard(new_layout.size()).expect("capacity overflow");
603+
alloc_guard(new_layout.size()).unwrap_or_else(|_| capacity_overflow());
604604
match self.a.grow_in_place(
605605
NonNull::from(self.ptr).as_opaque(), old_layout, new_layout.size(),
606606
) {
@@ -732,6 +732,13 @@ fn alloc_guard(alloc_size: usize) -> Result<(), CollectionAllocErr> {
732732
}
733733
}
734734

735+
// One central function responsible for reporting capacity overflows. This'll
736+
// ensure that the code generation related to these panics is minimal as there's
737+
// only one location which panics rather than a bunch throughout the module.
738+
fn capacity_overflow() -> ! {
739+
panic!("capacity overflow")
740+
}
741+
735742
#[cfg(test)]
736743
mod tests {
737744
use super::*;

src/libcore/fmt/mod.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -1212,7 +1212,11 @@ impl<'a> Formatter<'a> {
12121212
// truncation. However other flags like `fill`, `width` and `align`
12131213
// must act as always.
12141214
if let Some((i, _)) = s.char_indices().skip(max).next() {
1215-
&s[..i]
1215+
// LLVM here can't prove that `..i` won't panic `&s[..i]`, but
1216+
// we know that it can't panic. Use `get` + `unwrap_or` to avoid
1217+
// `unsafe` and otherwise don't emit any panic-related code
1218+
// here.
1219+
s.get(..i).unwrap_or(&s)
12161220
} else {
12171221
&s
12181222
}

src/libcore/panic.rs

+19-3
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,17 @@ impl<'a> PanicInfo<'a> {
4949
and related macros",
5050
issue = "0")]
5151
#[doc(hidden)]
52-
pub fn internal_constructor(payload: &'a (Any + Send),
53-
message: Option<&'a fmt::Arguments<'a>>,
52+
#[inline]
53+
pub fn internal_constructor(message: Option<&'a fmt::Arguments<'a>>,
5454
location: Location<'a>)
5555
-> Self {
56-
PanicInfo { payload, location, message }
56+
PanicInfo { payload: &(), location, message }
57+
}
58+
59+
#[doc(hidden)]
60+
#[inline]
61+
pub fn set_payload(&mut self, info: &'a (Any + Send)) {
62+
self.payload = info;
5763
}
5864

5965
/// Returns the payload associated with the panic.
@@ -251,3 +257,13 @@ impl<'a> fmt::Display for Location<'a> {
251257
write!(formatter, "{}:{}:{}", self.file, self.line, self.col)
252258
}
253259
}
260+
261+
/// An internal trait used by libstd to pass data from libstd to `panic_unwind`
262+
/// and other panic runtimes. Not intended to be stabilized any time soon, do
263+
/// not use.
264+
#[unstable(feature = "std_internals", issue = "0")]
265+
#[doc(hidden)]
266+
pub unsafe trait BoxMeUp {
267+
fn box_me_up(&mut self) -> *mut (Any + Send);
268+
fn get(&mut self) -> &(Any + Send);
269+
}

src/libpanic_abort/lib.rs

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

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

src/libpanic_unwind/lib.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,15 @@
2929
html_root_url = "https://doc.rust-lang.org/nightly/",
3030
issue_tracker_base_url = "https://github.com/rust-lang/rust/issues/")]
3131

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

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

52+
use alloc::boxed::Box;
5053
use core::intrinsics;
5154
use core::mem;
5255
use core::raw;
56+
use core::panic::BoxMeUp;
5357

5458
// Rust runtime's startup objects depend on these symbols, so make them public.
5559
#[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),
112116
// implementation.
113117
#[no_mangle]
114118
#[unwind(allowed)]
115-
pub unsafe extern "C" fn __rust_start_panic(data: usize, vtable: usize) -> u32 {
116-
imp::panic(mem::transmute(raw::TraitObject {
117-
data: data as *mut (),
118-
vtable: vtable as *mut (),
119-
}))
119+
pub unsafe extern "C" fn __rust_start_panic(payload: usize) -> u32 {
120+
let payload = payload as *mut &mut BoxMeUp;
121+
imp::panic(Box::from_raw((*payload).box_me_up()))
120122
}

src/libstd/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@
292292
#![feature(rand)]
293293
#![feature(raw)]
294294
#![feature(rustc_attrs)]
295+
#![feature(std_internals)]
295296
#![feature(stdsimd)]
296297
#![feature(shrink_to)]
297298
#![feature(slice_bytes)]

0 commit comments

Comments
 (0)