Unnamed repository; edit this file 'description' to name the repository.
Rollup merge of #155338 - cezarbbb:staticlib-symbol-hygiene, r=petrochenkov
Staticlib hide internal symbols
According to issue rust-lang/rust#104707, when building a staticlib, all Rust internal symbols — mangled symbols, `#[rustc_std_internal_symbol]` items, allocator shims, etc. — leak out of the static archive. In contrast, cdylib correctly exports only `#[no_mangle]` symbols via a linker version script.
`-Zstaticlib-hide-internal-symbols` directly post-processes ELF object files in the archive: parsing the `SHT_SYMTAB` sections and setting `STV_HIDDEN` visibility on any `GLOBAL/WEAK` defined symbol that is not in the exported symbol set, without changing the binding. This is an in-place modification (only writing the st_other byte per matching entry), with zero overhead.
Supported on ELF targets (Linux, BSD, etc.) and Apple targets (macOS, iOS, etc.). On unsupported targets (Windows), a warning is emitted and the flag has no effect.
**Update**: The rename counterpart (`-Zstaticlib-rename-internal-symbols`) is in rust-lang/rust#156950.
The test code are as follows:
1.a std rust staticlib:
```rust
use std::collections::HashMap;
use std::panic::{catch_unwind, AssertUnwindSafe};
#[no_mangle]
pub extern "C" fn my_add(a: i32, b: i32) -> i32 { a + b }
#[no_mangle]
pub extern "C" fn my_hash_lookup(key: u64) -> u64 {
let mut map = HashMap::new();
for i in 0..100u64 { map.insert(i, i.wrapping_mul(2654435761)); }
*map.get(&key).unwrap_or(&0)
}
pub fn internal_reverse(s: &str) -> String { s.chars().rev().collect() }
#[no_mangle]
pub extern "C" fn my_format_number(n: i32) -> i32 {
let s = format!("number: {}", n); s.len() as i32
}
#[no_mangle]
pub extern "C" fn my_safe_div(a: i32, b: i32) -> i32 {
match catch_unwind(AssertUnwindSafe(|| {
if b == 0 { panic!("division by zero!"); }
a / b
})) {
Ok(result) => result,
Err(_) => -1,
}
}
#[no_mangle]
pub extern "C" fn my_uncaught_panic() { panic!("uncaught panic across FFI"); }
```
1.b downstream c program:
```c
extern int my_add(int a, int b);
extern unsigned long my_hash_lookup(unsigned long key);
extern int my_format_number(int n);
extern int my_safe_div(int a, int b);
extern void my_uncaught_panic(void);
int main() {
int failures = 0;
if (my_add(10, 20) != 30) failures++;
if (my_hash_lookup(5) != 5UL * 2654435761UL) failures++;
if (my_format_number(42) != 10) failures++;
if (my_safe_div(100, 5) != 20) failures++;
if (my_safe_div(100, 0) != -1) failures++;
pid_t pid = fork();
if (pid == 0) { alarm(5); my_uncaught_panic(); _exit(0); }
else { waitpid(pid, &status, 0); }
return failures;
}
```
The test results with different compiler flags(which might cause binary size reduction) are as follows:
1.c result with `-Zstaticlib-hide-internal-symbols`
```
settings OFF ON -Zsave ALL OFF.dynsym ON.dynsym
------------------------------------------------------------------------
default 1.7M 1.5M 204K (12%) 1735 5 1730
lto_thin 616K 584K 33K (5%) 246 5 241
lto_fat 525K 525K 0 (0%) 6 5 1
opt_s 1.7M 1.5M 204K (12%) 1735 5 1730
opt_z 1.7M 1.5M 204K (12%) 1735 5 1730
lto_thin_z 602K 570K 32K (5%) 246 5 241
lto_fat_z 514K 514K 0 (0%) 6 5 1
full 514K 514K 0 (0%) 6 5 1
```
1.d result with `-Zstaticlib-hide-internal-symbols + -Zstaticlib-rename-internal-symbols`
```
settings OFF ON -Zsave ALL OFF.dynsym ON.dynsym
------------------------------------------------------------------------
default 1.7M 1.5M 162K (9%) 1735 5 1730
lto_thin 616K 599K 18K (2%) 246 5 241
lto_fat 525K 535K -1% (-1%) 6 5 1
opt_s 1.7M 1.5M 162K (9%) 1735 5 1730
opt_z 1.7M 1.5M 162K (9%) 1735 5 1730
lto_thin_z 602K 585K 18K (2%) 246 5 241
lto_fat_z 514K 524K -1% (-1%) 6 5 1
full 514K 523K -1% (-1%) 6 5 1
```
2.a no_std rust staticlib
```rust
#![no_std]
#![feature(core_intrinsics)]
use core::panic::PanicInfo;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! { loop {} }
#[no_mangle]
pub extern "C" fn embedded_add(a: i32, b: i32) -> i32 { a.wrapping_add(b) }
#[no_mangle]
pub extern "C" fn embedded_checksum(data: *const u8, len: usize) -> u8 {
if data.is_null() { return 0; }
let slice = unsafe { core::slice::from_raw_parts(data, len) };
let mut sum: u8 = 0;
for &byte in slice { sum = sum.wrapping_add(byte); }
sum
}
fn internal_helper() -> i32 { 42 }
#[no_mangle]
pub extern "C" fn call_internal() -> i32 { internal_helper() }
#[no_mangle]
pub extern "C" fn embedded_trigger_abort() { core::intrinsics::abort(); }
```
2.b downstream c program
```c
extern int embedded_add(int a, int b);
extern unsigned char embedded_checksum(const unsigned char *data, unsigned long len);
extern int call_internal(void);
extern void embedded_trigger_abort(void);
int main() {
int failures = 0;
if (embedded_add(10, 20) != 30) failures++;
unsigned char data[] = {1, 2, 3};
if (embedded_checksum(data, 3) != 6) failures++;
if (call_internal() != 42) failures++;
pid_t pid = fork();
if (pid == 0) { embedded_trigger_abort(); _exit(0); }
else { waitpid(pid, &status, 0); }
return failures;
}
```
The test results with different compiler flags(which might cause binary size reduction) are as follows:
2.c result with `-Zstaticlib-hide-internal-symbols`
```
settings OFF ON -Zsave ALL OFF.dynsym ON.dynsym
------------------------------------------------------------------------
default 485K 429K 56K (11%) 490 4 486
lto_thin 180K 180K 0 (0%) 4 4 0
lto_fat 179K 179K 0 (0%) 4 4 0
opt_s 485K 429K 56K (11%) 490 4 486
opt_z 485K 429K 56K (11%) 490 4 486
lto_thin_z 180K 180K 0 (0%) 4 4 0
lto_fat_z 179K 179K 0 (0%) 4 4 0
full 179K 179K 0 (0%) 4 4 0
```
2.d result with `-Zstaticlib-hide-internal-symbols + -Zstaticlib-rename-internal-symbols`
```
settings OFF ON -Zsave ALL OFF.dynsym ON.dynsym
------------------------------------------------------------------------
default 485K 447K 39K (7%) 490 4 486
lto_thin 180K 189K -5% (-5%) 4 4 0
lto_fat 179K 189K -5% (-5%) 4 4 0
opt_s 485K 448K 38K (7%) 490 4 486
opt_z 485K 448K 38K (7%) 490 4 486
lto_thin_z 180K 189K -5% (-5%) 4 4 0
lto_fat_z 179K 189K -5% (-5%) 4 4 0
full 179K 189K -5% (-5%) 4 4 0
```
Test results show that this compiler option is beneficial for scenarios where LTO cannot be enabled.
r? @bjorn3 @petrochenkov