Skip to content

Commit 406e171

Browse files
authored
Rollup merge of rust-lang#95604 - nbdd0121:used2, r=petrochenkov
Generate synthetic object file to ensure all exported and used symbols participate in the linking Fix rust-lang#50007 and rust-lang#47384 This is the synthetic object file approach that I described in rust-lang#95363 (comment), allowing all exported and used symbols to be linked while still allowing them to be GCed. Related rust-lang#93791, rust-lang#95363 r? `@petrochenkov` cc `@carbotaniuman`
2 parents 0ca05f1 + 98cd818 commit 406e171

File tree

22 files changed

+293
-57
lines changed

22 files changed

+293
-57
lines changed

compiler/rustc_codegen_llvm/src/back/lto.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use rustc_errors::{FatalError, Handler};
1616
use rustc_hir::def_id::LOCAL_CRATE;
1717
use rustc_middle::bug;
1818
use rustc_middle::dep_graph::WorkProduct;
19-
use rustc_middle::middle::exported_symbols::SymbolExportLevel;
19+
use rustc_middle::middle::exported_symbols::{SymbolExportInfo, SymbolExportLevel};
2020
use rustc_session::cgu_reuse_tracker::CguReuse;
2121
use rustc_session::config::{self, CrateType, Lto};
2222
use tracing::{debug, info};
@@ -55,8 +55,8 @@ fn prepare_lto(
5555
Lto::No => panic!("didn't request LTO but we're doing LTO"),
5656
};
5757

58-
let symbol_filter = &|&(ref name, level): &(String, SymbolExportLevel)| {
59-
if level.is_below_threshold(export_threshold) {
58+
let symbol_filter = &|&(ref name, info): &(String, SymbolExportInfo)| {
59+
if info.level.is_below_threshold(export_threshold) {
6060
Some(CString::new(name.as_str()).unwrap())
6161
} else {
6262
None

compiler/rustc_codegen_ssa/src/back/link.rs

+69
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use rustc_errors::{ErrorGuaranteed, Handler};
77
use rustc_fs_util::fix_windows_verbatim_for_gcc;
88
use rustc_hir::def_id::CrateNum;
99
use rustc_middle::middle::dependency_format::Linkage;
10+
use rustc_middle::middle::exported_symbols::SymbolExportKind;
1011
use rustc_session::config::{self, CFGuard, CrateType, DebugInfo, LdImpl, Strip};
1112
use rustc_session::config::{OutputFilenames, OutputType, PrintRequest, SplitDwarfKind};
1213
use rustc_session::cstore::DllImport;
@@ -1655,6 +1656,67 @@ fn add_post_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor
16551656
}
16561657
}
16571658

1659+
/// Add a synthetic object file that contains reference to all symbols that we want to expose to
1660+
/// the linker.
1661+
///
1662+
/// Background: we implement rlibs as static library (archives). Linkers treat archives
1663+
/// differently from object files: all object files participate in linking, while archives will
1664+
/// only participate in linking if they can satisfy at least one undefined reference (version
1665+
/// scripts doesn't count). This causes `#[no_mangle]` or `#[used]` items to be ignored by the
1666+
/// linker, and since they never participate in the linking, using `KEEP` in the linker scripts
1667+
/// can't keep them either. This causes #47384.
1668+
///
1669+
/// To keep them around, we could use `--whole-archive` and equivalents to force rlib to
1670+
/// participate in linking like object files, but this proves to be expensive (#93791). Therefore
1671+
/// we instead just introduce an undefined reference to them. This could be done by `-u` command
1672+
/// line option to the linker or `EXTERN(...)` in linker scripts, however they does not only
1673+
/// introduce an undefined reference, but also make them the GC roots, preventing `--gc-sections`
1674+
/// from removing them, and this is especially problematic for embedded programming where every
1675+
/// byte counts.
1676+
///
1677+
/// This method creates a synthetic object file, which contains undefined references to all symbols
1678+
/// that are necessary for the linking. They are only present in symbol table but not actually
1679+
/// used in any sections, so the linker will therefore pick relevant rlibs for linking, but
1680+
/// unused `#[no_mangle]` or `#[used]` can still be discard by GC sections.
1681+
fn add_linked_symbol_object(
1682+
cmd: &mut dyn Linker,
1683+
sess: &Session,
1684+
tmpdir: &Path,
1685+
symbols: &[(String, SymbolExportKind)],
1686+
) {
1687+
if symbols.is_empty() {
1688+
return;
1689+
}
1690+
1691+
let Some(mut file) = super::metadata::create_object_file(sess) else {
1692+
return;
1693+
};
1694+
1695+
for (sym, kind) in symbols.iter() {
1696+
file.add_symbol(object::write::Symbol {
1697+
name: sym.clone().into(),
1698+
value: 0,
1699+
size: 0,
1700+
kind: match kind {
1701+
SymbolExportKind::Text => object::SymbolKind::Text,
1702+
SymbolExportKind::Data => object::SymbolKind::Data,
1703+
SymbolExportKind::Tls => object::SymbolKind::Tls,
1704+
},
1705+
scope: object::SymbolScope::Unknown,
1706+
weak: false,
1707+
section: object::write::SymbolSection::Undefined,
1708+
flags: object::SymbolFlags::None,
1709+
});
1710+
}
1711+
1712+
let path = tmpdir.join("symbols.o");
1713+
let result = std::fs::write(&path, file.write().unwrap());
1714+
if let Err(e) = result {
1715+
sess.fatal(&format!("failed to write {}: {}", path.display(), e));
1716+
}
1717+
cmd.add_object(&path);
1718+
}
1719+
16581720
/// Add object files containing code from the current crate.
16591721
fn add_local_crate_regular_objects(cmd: &mut dyn Linker, codegen_results: &CodegenResults) {
16601722
for obj in codegen_results.modules.iter().filter_map(|m| m.object.as_ref()) {
@@ -1798,6 +1860,13 @@ fn linker_with_args<'a, B: ArchiveBuilder<'a>>(
17981860
// Sanitizer libraries.
17991861
add_sanitizer_libraries(sess, crate_type, cmd);
18001862

1863+
add_linked_symbol_object(
1864+
cmd,
1865+
sess,
1866+
tmpdir,
1867+
&codegen_results.crate_info.linked_symbols[&crate_type],
1868+
);
1869+
18011870
// Object code from the current crate.
18021871
// Take careful note of the ordering of the arguments we pass to the linker
18031872
// here. Linkers will assume that things on the left depend on things to the

compiler/rustc_codegen_ssa/src/back/linker.rs

+50-22
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use std::{env, mem, str};
1212

1313
use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
1414
use rustc_middle::middle::dependency_format::Linkage;
15+
use rustc_middle::middle::exported_symbols::{ExportedSymbol, SymbolExportInfo, SymbolExportKind};
1516
use rustc_middle::ty::TyCtxt;
1617
use rustc_serialize::{json, Encoder};
1718
use rustc_session::config::{self, CrateType, DebugInfo, LinkerPluginLto, Lto, OptLevel, Strip};
@@ -1518,6 +1519,29 @@ impl<'a> L4Bender<'a> {
15181519
}
15191520
}
15201521

1522+
fn for_each_exported_symbols_include_dep<'tcx>(
1523+
tcx: TyCtxt<'tcx>,
1524+
crate_type: CrateType,
1525+
mut callback: impl FnMut(ExportedSymbol<'tcx>, SymbolExportInfo, CrateNum),
1526+
) {
1527+
for &(symbol, info) in tcx.exported_symbols(LOCAL_CRATE).iter() {
1528+
callback(symbol, info, LOCAL_CRATE);
1529+
}
1530+
1531+
let formats = tcx.dependency_formats(());
1532+
let deps = formats.iter().find_map(|(t, list)| (*t == crate_type).then_some(list)).unwrap();
1533+
1534+
for (index, dep_format) in deps.iter().enumerate() {
1535+
let cnum = CrateNum::new(index + 1);
1536+
// For each dependency that we are linking to statically ...
1537+
if *dep_format == Linkage::Static {
1538+
for &(symbol, info) in tcx.exported_symbols(cnum).iter() {
1539+
callback(symbol, info, cnum);
1540+
}
1541+
}
1542+
}
1543+
}
1544+
15211545
pub(crate) fn exported_symbols(tcx: TyCtxt<'_>, crate_type: CrateType) -> Vec<String> {
15221546
if let Some(ref exports) = tcx.sess.target.override_export_symbols {
15231547
return exports.iter().map(ToString::to_string).collect();
@@ -1526,34 +1550,38 @@ pub(crate) fn exported_symbols(tcx: TyCtxt<'_>, crate_type: CrateType) -> Vec<St
15261550
let mut symbols = Vec::new();
15271551

15281552
let export_threshold = symbol_export::crates_export_threshold(&[crate_type]);
1529-
for &(symbol, level) in tcx.exported_symbols(LOCAL_CRATE).iter() {
1530-
if level.is_below_threshold(export_threshold) {
1531-
symbols.push(symbol_export::symbol_name_for_instance_in_crate(
1532-
tcx,
1533-
symbol,
1534-
LOCAL_CRATE,
1535-
));
1553+
for_each_exported_symbols_include_dep(tcx, crate_type, |symbol, info, cnum| {
1554+
if info.level.is_below_threshold(export_threshold) {
1555+
symbols.push(symbol_export::symbol_name_for_instance_in_crate(tcx, symbol, cnum));
15361556
}
1537-
}
1557+
});
15381558

1539-
let formats = tcx.dependency_formats(());
1540-
let deps = formats.iter().find_map(|(t, list)| (*t == crate_type).then_some(list)).unwrap();
1541-
1542-
for (index, dep_format) in deps.iter().enumerate() {
1543-
let cnum = CrateNum::new(index + 1);
1544-
// For each dependency that we are linking to statically ...
1545-
if *dep_format == Linkage::Static {
1546-
// ... we add its symbol list to our export list.
1547-
for &(symbol, level) in tcx.exported_symbols(cnum).iter() {
1548-
if !level.is_below_threshold(export_threshold) {
1549-
continue;
1550-
}
1559+
symbols
1560+
}
15511561

1552-
symbols.push(symbol_export::symbol_name_for_instance_in_crate(tcx, symbol, cnum));
1553-
}
1562+
pub(crate) fn linked_symbols(
1563+
tcx: TyCtxt<'_>,
1564+
crate_type: CrateType,
1565+
) -> Vec<(String, SymbolExportKind)> {
1566+
match crate_type {
1567+
CrateType::Executable | CrateType::Cdylib => (),
1568+
CrateType::Staticlib | CrateType::ProcMacro | CrateType::Rlib | CrateType::Dylib => {
1569+
return Vec::new();
15541570
}
15551571
}
15561572

1573+
let mut symbols = Vec::new();
1574+
1575+
let export_threshold = symbol_export::crates_export_threshold(&[crate_type]);
1576+
for_each_exported_symbols_include_dep(tcx, crate_type, |symbol, info, cnum| {
1577+
if info.level.is_below_threshold(export_threshold) || info.used {
1578+
symbols.push((
1579+
symbol_export::symbol_name_for_instance_in_crate(tcx, symbol, cnum),
1580+
info.kind,
1581+
));
1582+
}
1583+
});
1584+
15571585
symbols
15581586
}
15591587

compiler/rustc_codegen_ssa/src/back/metadata.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ fn search_for_metadata<'a>(
9494
.map_err(|e| format!("failed to read {} section in '{}': {}", section, path.display(), e))
9595
}
9696

97-
fn create_object_file(sess: &Session) -> Option<write::Object<'static>> {
97+
pub(crate) fn create_object_file(sess: &Session) -> Option<write::Object<'static>> {
9898
let endianness = match sess.target.options.endian {
9999
Endian::Little => Endianness::Little,
100100
Endian::Big => Endianness::Big,

0 commit comments

Comments
 (0)