Currently assertions cause executable to be bloated up by various formatting-related symbols like
0.5% 2.8% 1020B std <core::fmt::builders::PadAdapter<'_> as core::fmt::Write>::write_str
0.4% 2.3% 860B std core::char::methods::<impl char>::escape_debug
0.4% 2.2% 804B std core::fmt::Formatter::pad_integral
, causing executables that carefully choose functions to call in order to stay under X kilobytes to suddenly grow by about 40-150 kilobytes. Additionally, pthread_* symbols appear in otherwise single-threaded module. Symbol count may jump by about 800.
Shall there be some mode (like panic = "really-abort") or a libstd feature settable in Xargo.toml that makes all panics just do plain raw abort without trying to collect backtrace or print anything at all?
Looks like making panicking::{continue_panic_fmt,begin_panic} just do the abort makes it small again.
Is a pull request about this welcome?
With the new feature merged in, one can opt out formatting and panic handling using panic_immediate_abort and #![no_main]. This can bring static executable size under 20 kilobytes.
You need to
#![no_main] #[no_mangle] pub fn main(...) {...},std = {default-features=false, features=["panic_immediate_abort"]}
line in Xargo.toml,println! or format! yourself (including in libs).All panics and failed asserts will just cause Illegal Instruction.
Hey @vi, I'm so glad you got this in! I followed your instructions above and my binaries are getting smaller. However, ldd tells me that there's still a libpthread dependency in my compiled (single-threaded) binaries. Have you succeeded in getting rid of that?
Try the minimal example:
src/main.rs:
#![no_main]
#[no_mangle]
pub fn main() {
}
Xargo.toml:
[dependencies]
std = {default-features=false, features=["panic_immediate_abort"]}
Cargo.toml:
[package]
name = "tiny"
version = "0.1.0"
authors = []
edition = "2018"
[profile.release]
opt-level = "s"
debug = false
rpath = false
lto = true
debug-assertions = false
codegen-units = 1
panic = 'abort'
incremental = false
Commands:
$ xargo build --release --target=x86_64-unknown-linux-gnu
Updating crates.io index
Compiling cc v1.0.25
Compiling build_helper v0.1.0 (/nix/rust/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/build_helper)
Compiling core v0.0.0 (/nix/rust/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libcore)
Compiling unwind v0.0.0 (/nix/rust/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libunwind)
Compiling compiler_builtins v0.0.0 (/nix/rust/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/rustc/compiler_builtins_shim)
Compiling cmake v0.1.35
Compiling std v0.0.0 (/nix/rust/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libstd)
Compiling rustc_asan v0.0.0 (/nix/rust/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/librustc_asan)
Compiling rustc_tsan v0.0.0 (/nix/rust/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/librustc_tsan)
Compiling rustc_msan v0.0.0 (/nix/rust/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/librustc_msan)
Compiling rustc_lsan v0.0.0 (/nix/rust/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/librustc_lsan)
Compiling libc v0.0.0 (/nix/rust/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/rustc/libc_shim)
Compiling alloc v0.0.0 (/nix/rust/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/liballoc)
Compiling panic_abort v0.0.0 (/nix/rust/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/src/libpanic_abort)
Finished release [optimized] target(s) in 1m 01s
Compiling tiny v0.1.0 (/tmp/rstiny)
Finished release [optimized] target(s) in 0.52s
$ strip target/x86_64-unknown-linux-gnu/release/tiny -o tiny
$ ldd tiny
linux-vdso.so.1 (0x000003fdde54f000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x000003d847699000)
/lib64/ld-linux-x86-64.so.2 (0x0000031694ddc000)
$ ls -lh tiny
-rwxrwxr-x 1 vi vi 6.0K Dec 10 00:21 tiny
Is it the same for you?
You're right, I get the same as you. But trying that helped me figure out that adding the libc crate seems to be what adds the libpthread dependency, so thanks!
Strange.
#![no_main]
#[no_mangle]
pub unsafe fn main() {
libc::_exit(0);
}
does not include pthread, but
#![no_main]
#[no_mangle]
pub unsafe fn main() {
libc::write(1, b"Hello, world\n\0" as *const u8 as *const libc::c_void, 14);
libc::_exit(0);
}
does...
This one again does not include pthread:
#![no_main]
#[no_mangle]
pub unsafe fn main() {
libc::syscall(libc::SYS_write, 1, b"Hello, world\n\0" as *const u8 as *const libc::c_void, 14);
libc::_exit(0);
}
I got rid of pthread by depending on libc with default-features = false.
Before that, I noticed that the libc crate functions that led to libpthread being linked in (besides write, open and close seem to do it as well, but not several others like system, creat, or openat) seem to be precisely those functions that also have definitions in libpthread, so I think without default-features = false, those functions get linked to their libpthread implementations, not their single-threaded libc implementations. I don't really understand how that happens; I found a complex set of conditional linker attributes in libc which seem like they might control this, but turning off default features means that use_std becomes false, in which case it looks like the pthread dependency should become enabled instead of disabled, despite what I'm seeing. :man_shrugging: I'll leave that for the next wanderer to puzzle over.
@vi In your tiny example, is there a reason you used opt-level = "s" instead of opt-level = "z"? I thought "z" was always strictly better at minimizing the size.
@johnthagen , I don't think s vs z is relevant for such a small example. Unlike most other measures to reduce size bloat, z can cause more performance loss when compared to s. And I don't remember observing significant size changes between s and z. Is the differene only about loop unrolling or there's more?
Also the example was investigation of when it links to libpthread and when not, so small size wasn't a focus. opt-level="s" is just inserted by default in my xargoize script.
Is the differene only about loop unrolling or there's more?
That's the only thing I've heard of. The Cargo reference just says:
# 's' attempts to reduce size, 'z' reduces size even more.
@vi Looks like the latest nightly (rustc 1.32.0-nightly (f4a421ee3 2018-12-13)) might have broken this example / Xargo?
https://travis-ci.org/johnthagen/min-sized-rust/jobs/467982878#L498
@johnthagen , Indeed: https://github.com/rust-lang/rust/pull/56092
this almost for sure breaks out-of-tree std-building tools like xargo and cargo-xbuild
Fortunately, something like a fix seems to be already available: https://github.com/japaric/xargo/pull/229 https://github.com/japaric/xargo/pull/228.
Most helpful comment
With the new feature merged in, one can opt out formatting and panic handling using
panic_immediate_abortand#![no_main]. This can bring static executable size under 20 kilobytes.You need to
#![no_main] #[no_mangle] pub fn main(...) {...},std = {default-features=false, features=["panic_immediate_abort"]}line inXargo.toml,println!orformat!yourself (including in libs).All panics and failed asserts will just cause Illegal Instruction.