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

Unused symbols being exported #112533

Closed
p2jason opened this issue Jun 11, 2023 · 5 comments
Closed

Unused symbols being exported #112533

p2jason opened this issue Jun 11, 2023 · 5 comments
Labels
-Zbuild-std Unstable Cargo option: Compile the standard library yourself. A-linkage Area: linking into static, shared libraries and binaries I-heavy Issue: Problems and improvements with respect to binary size of generated code. requires-nightly This issue requires a nightly compiler in some way.

Comments

@p2jason
Copy link

p2jason commented Jun 11, 2023

Hi there,

I am writing a small library (staticlib) for an OS project. This library only needs to export one function and is linked with a program written in assembly that ends up calling that function. Unfortunately, rustc is exporting way too many functions and the final binary ends up being way larger than it needs to be.

I know this is an issue that others have had before, such as #18541 and #37530, but none of the solutions I've found so far have workws for me. I've also tried using version scripts and I've tried stripping the library, but those either didn't work or broke something in the process.

Here is a small reproducible setup:

File .cargo/config.toml:

[unstable]
build-std-features = ["compiler-builtins-mem"]
build-std = ["core", "compiler_builtins"]

File src/lib.rs:

#![no_std]
#![no_main]

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} }

#[no_mangle]
pub extern "C" fn _test_start() {
	
}

File Cargo.toml:

[package]
name = "test"
version = "0.1.0"

[lib]
crate-type = ["staticlib"]

[profile.release]
panic = "abort"

[profile.dev]
panic = "abort"

I am also using the nightly version of rust.

Is there a way to get the rust compiler to export only the functions I need?

@saethlin
Copy link
Member

saethlin commented Jun 12, 2023

Are the exported symbols you don't want all from compiler_builtins? That's what I see, and it reminds me of this:

rust/Cargo.toml

Lines 74 to 84 in 77dba22

# For compiler-builtins we always use a high number of codegen units.
# The goal here is to place every single intrinsic into its own object
# file to avoid symbol clashes with the system libgcc if possible. Note
# that this number doesn't actually produce this many object files, we
# just don't create more than this number of object files.
#
# It's a bit of a bummer that we have to pass this here, unfortunately.
# Ideally this would be specified through an env var to Cargo so Cargo
# knows how many CGUs are for this specific crate, but for now
# per-crate configuration isn't specifiable in the environment.
codegen-units = 10000

So it would make sense to me if the whole of compiler_builtins gets pasted into your archive file.

I tried building this as a cdylib and got reasonable-looking code size. I feel like the linker is supposed to clean the mess up, and maybe that's the right place to debug? Can you provide instructions for linking this .a with the rest of the program?

@jyn514 jyn514 added A-linkage Area: linking into static, shared libraries and binaries requires-nightly This issue requires a nightly compiler in some way. -Zbuild-std Unstable Cargo option: Compile the standard library yourself. I-heavy Issue: Problems and improvements with respect to binary size of generated code. labels Jun 12, 2023
@p2jason
Copy link
Author

p2jason commented Jun 12, 2023

Most of the symbols I'm getting look something like this:

__ZN4core3fmt3num50_$LT$impl$u20$core..fmt..Debug$u20$for$u20$u32$GT$3fmt17h085b56546a70466dE

or like this:

L_anon.f807f9d1b370fe867717655e6c947961.31

so, I'm guessing most of them are coming from core.

I tried setting codegen-units to 1 but there wasn't must of a difference. The final binary only dropped from 511KB to 488KB. I also tried changing the type of the library from a staticlib to a cdylib and I did get a pretty small binary. I've observed a similar behavior before, but with executables instead of dynamic libraries. Unfortunately, this only works for very small programs. If you increase the size of your code by just a little, the size of the binary jumps up to what it was before.

Here's the smallest src/lib.rs where the final binary is still quite large:

#![no_std]
#![no_main]

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} }

use core::ptr::{read_volatile, write_volatile};

type ScreenCells = [[u16; 80]; 25];

struct Screen {
    cursor_row: usize,
    cursor_col: usize,
    chars: &'static mut ScreenCells,
}

impl Screen {
    fn append_char(&mut self, ascii_char: u8) {
        if self.cursor_col >= 80 {
            self.cursor_col = 0;
            self.cursor_row += 1;
        }

        if ascii_char == b'\n' {
            return;
        }

        unsafe { write_volatile(&mut self.chars[self.cursor_row][self.cursor_col], ascii_char as u16); }
        
        self.cursor_col += 1;
    }
}

#[no_mangle]
pub extern "C" fn _test_start() {
    let mut buffer = Screen {
        cursor_row: 0,
        cursor_col: 0,

        chars: unsafe { &mut *(0xb8000 as *mut ScreenCells) },
    };
    
    for i in 0..100 {
        buffer.append_char(i as u8);
    }
}

(anything simpler than this, and the final binary drops drastically in size)

As for linking the library to the rest of the program, there isn't much to it. I have a simple file written in assembly:

[bits 32]
[extern _test_start]
call _test_start

which is compiled into an elf file with nasm:

nasm entry.asm -f elf32 -o from_nasm.o

and then gets linked with the library as such:

ld.lld -o final.bin -Ttext 0x7e00 from_nasm.o from_rust.o --oformat binary 

@saethlin
Copy link
Member

Oh, formatting stuff! Enable the standard library feature panic_immediate_abort. It exists to fix this problem. And please do open an issue if it stops working or works incompletely.

@bjorn3
Copy link
Member

bjorn3 commented Jun 13, 2023

Can you try using --gc-sections? This should remove unused functions from the final executable. This is what rustc passes to the linker too.

@p2jason
Copy link
Author

p2jason commented Jun 13, 2023

Ok, both seem to work!

I added the panic_immediate_abort option to my .cargo/config.toml file and, although the size of the staticlib is not that much smaller, after linking it with the assembly program, the final binary only contains the functions I need.

When I link the assembly program and the staticlib with --gc-sections (without panic_immediate_abort), there are a few functions that still get linked into the final binary even though I don't use them. That being said, it is still way fewer than before.

Hopefully, I won't have any more link issues, but I'll let you know if anything comes up.

Thanks for the help!

@p2jason p2jason closed this as completed Jun 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
-Zbuild-std Unstable Cargo option: Compile the standard library yourself. A-linkage Area: linking into static, shared libraries and binaries I-heavy Issue: Problems and improvements with respect to binary size of generated code. requires-nightly This issue requires a nightly compiler in some way.
Projects
None yet
Development

No branches or pull requests

4 participants