Add rlib digest to identify Rust object files

This commit is contained in:
mehdiakiki
2026-04-05 19:59:30 -04:00
parent 338dff3e3a
commit 1303caee2b
7 changed files with 97 additions and 44 deletions
+4 -2
View File
@@ -24,9 +24,10 @@
use gccjit::OutputKind;
use object::read::archive::ArchiveFile;
use rustc_codegen_ssa::back::lto::SerializedModule;
use rustc_codegen_ssa::back::rmeta_link;
use rustc_codegen_ssa::back::write::{CodegenContext, FatLtoInput, SharedEmitter};
use rustc_codegen_ssa::traits::*;
use rustc_codegen_ssa::{CompiledModule, ModuleCodegen, ModuleKind, looks_like_rust_object_file};
use rustc_codegen_ssa::{CompiledModule, ModuleCodegen, ModuleKind};
use rustc_data_structures::memmap::Mmap;
use rustc_data_structures::profiling::SelfProfilerRef;
use rustc_errors::{DiagCtxt, DiagCtxtHandle};
@@ -63,6 +64,7 @@ fn prepare_lto(each_linked_rlib_for_lto: &[PathBuf], dcx: DiagCtxtHandle<'_>) ->
let archive_data = unsafe {
Mmap::map(File::open(path).expect("couldn't open rlib")).expect("couldn't map rlib")
};
let digest = rmeta_link::read_from_data(&archive_data, path).unwrap();
let archive = ArchiveFile::parse(&*archive_data).expect("wanted an rlib");
let obj_files = archive
.members()
@@ -71,7 +73,7 @@ fn prepare_lto(each_linked_rlib_for_lto: &[PathBuf], dcx: DiagCtxtHandle<'_>) ->
.ok()
.and_then(|c| std::str::from_utf8(c.name()).ok().map(|name| (name.trim(), c)))
})
.filter(|&(name, _)| looks_like_rust_object_file(name));
.filter(|&(name, _)| digest.rust_object_files.iter().any(|f| f == name));
for (name, child) in obj_files {
info!("adding bitcode from {}", name);
let path = tmp_path.path().join(name);
+4 -2
View File
@@ -8,11 +8,12 @@
use object::read::archive::ArchiveFile;
use object::{Object, ObjectSection};
use rustc_codegen_ssa::back::lto::{SerializedModule, ThinModule, ThinShared};
use rustc_codegen_ssa::back::rmeta_link;
use rustc_codegen_ssa::back::write::{
CodegenContext, FatLtoInput, SharedEmitter, TargetMachineFactoryFn, ThinLtoInput,
};
use rustc_codegen_ssa::traits::*;
use rustc_codegen_ssa::{CompiledModule, ModuleCodegen, ModuleKind, looks_like_rust_object_file};
use rustc_codegen_ssa::{CompiledModule, ModuleCodegen, ModuleKind};
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::memmap::Mmap;
use rustc_data_structures::profiling::SelfProfilerRef;
@@ -96,6 +97,7 @@ fn prepare_lto(
.expect("couldn't map rlib")
};
let archive = ArchiveFile::parse(&*archive_data).expect("wanted an rlib");
let digest = rmeta_link::read(&archive, &archive_data, &path).unwrap();
let obj_files = archive
.members()
.filter_map(|child| {
@@ -103,7 +105,7 @@ fn prepare_lto(
.ok()
.and_then(|c| std::str::from_utf8(c.name()).ok().map(|name| (name.trim(), c)))
})
.filter(|&(name, _)| looks_like_rust_object_file(name));
.filter(|&(name, _)| digest.rust_object_files.iter().any(|f| f == name));
for (name, child) in obj_files {
info!("adding bitcode from {}", name);
match get_bitcode_slice_from_object_data(
@@ -21,6 +21,7 @@
use tracing::trace;
use super::metadata::{create_compressed_metadata_file, search_for_section};
use super::rmeta_link::{self, RmetaLink};
use crate::common;
// Public for ArchiveBuilderBuilder::extract_bundled_libs
pub use crate::errors::ExtractBundledLibsError;
@@ -314,7 +315,7 @@ pub trait ArchiveBuilder {
fn add_archive(
&mut self,
archive: &Path,
skip: Box<dyn FnMut(&str) -> bool + 'static>,
skip: Box<dyn FnMut(&str, Option<&RmetaLink>) -> bool + 'static>,
) -> io::Result<()>;
fn build(self: Box<Self>, output: &Path) -> bool;
@@ -402,7 +403,7 @@ impl<'a> ArchiveBuilder for ArArchiveBuilder<'a> {
fn add_archive(
&mut self,
archive_path: &Path,
mut skip: Box<dyn FnMut(&str) -> bool + 'static>,
mut skip: Box<dyn FnMut(&str, Option<&RmetaLink>) -> bool + 'static>,
) -> io::Result<()> {
let mut archive_path = archive_path.to_path_buf();
if self.sess.target.llvm_target.contains("-apple-macosx")
@@ -418,13 +419,14 @@ fn add_archive(
let archive_map = unsafe { Mmap::map(File::open(&archive_path)?)? };
let archive = ArchiveFile::parse(&*archive_map)
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
let digest = rmeta_link::read(&archive, &archive_map, &archive_path);
let archive_index = self.src_archives.len();
for entry in archive.members() {
let entry = entry.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
let file_name = String::from_utf8(entry.name().to_vec())
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
if !skip(&file_name) {
if !skip(&file_name, digest.as_ref()) {
if entry.is_thin() {
let member_path = archive_path.parent().unwrap().join(Path::new(&file_name));
self.entries.push((file_name.into_bytes(), ArchiveEntry::File(member_path)));
+26 -19
View File
@@ -58,12 +58,9 @@
use super::linker::{self, Linker};
use super::metadata::{MetadataPosition, create_wrapper_file};
use super::rpath::{self, RPathConfig};
use super::{apple, versioned_llvm_target};
use super::{apple, rmeta_link, versioned_llvm_target};
use crate::base::needs_allocator_shim_for_linking;
use crate::{
CodegenLintLevels, CompiledModule, CompiledModules, CrateInfo, NativeLib, errors,
looks_like_rust_object_file,
};
use crate::{CodegenLintLevels, CompiledModule, CompiledModules, CrateInfo, NativeLib, errors};
pub fn ensure_removed(dcx: DiagCtxtHandle<'_>, path: &Path) {
if let Err(e) = fs::remove_file(path) {
@@ -329,8 +326,11 @@ fn link_rlib<'a>(
RlibFlavor::StaticlibBase => None,
};
let mut rust_object_files: Vec<String> = Vec::new();
for m in &compiled_modules.modules {
if let Some(obj) = m.object.as_ref() {
rust_object_files.push(obj.file_name().unwrap().to_str().unwrap().to_string());
ab.add_file(obj);
}
@@ -383,7 +383,7 @@ fn link_rlib<'a>(
packed_bundled_libs.push(wrapper_file);
} else {
let path = find_native_static_library(lib.name.as_str(), lib.verbatim, sess);
ab.add_archive(&path, Box::new(|_| false)).unwrap_or_else(|error| {
ab.add_archive(&path, Box::new(|_, _| false)).unwrap_or_else(|error| {
sess.dcx().emit_fatal(errors::AddNativeLibrary { library_path: path, error })
});
}
@@ -400,7 +400,7 @@ fn link_rlib<'a>(
tmpdir.as_ref(),
true,
) {
ab.add_archive(&output_path, Box::new(|_| false)).unwrap_or_else(|error| {
ab.add_archive(&output_path, Box::new(|_, _| false)).unwrap_or_else(|error| {
sess.dcx()
.emit_fatal(errors::AddNativeLibrary { library_path: output_path, error });
});
@@ -442,6 +442,16 @@ fn link_rlib<'a>(
ab.add_file(&lib)
}
// Add the rlib digest as the very last member. This records which archive
// members are Rust object files, replacing filename-based heuristics.
if matches!(flavor, RlibFlavor::Normal) {
let digest = rmeta_link::RmetaLink { rust_object_files };
let digest_data = digest.encode();
let (wrapper, _) = create_wrapper_file(sess, rmeta_link::SECTION.to_string(), &digest_data);
let digest_file = emit_wrapper_file(sess, &wrapper, tmpdir.as_ref(), rmeta_link::FILENAME);
ab.add_file(&digest_file);
}
ab
}
@@ -488,14 +498,14 @@ fn link_staticlib(
let bundled_libs: FxIndexSet<_> = native_libs.filter_map(|lib| lib.filename).collect();
ab.add_archive(
path,
Box::new(move |fname: &str| {
// Ignore metadata files, no matter the name.
if fname == METADATA_FILENAME {
Box::new(move |fname: &str, digest| {
// Ignore metadata and rlib digest files.
if fname == METADATA_FILENAME || fname == rmeta_link::FILENAME {
return true;
}
// Don't include Rust objects if LTO is enabled
if lto && looks_like_rust_object_file(fname) {
// Don't include Rust objects if LTO is enabled.
if lto && digest.is_some_and(|d| d.rust_object_files.iter().any(|f| f == fname)) {
return true;
}
@@ -516,7 +526,7 @@ fn link_staticlib(
for filename in relevant_libs.iter() {
let joined = tempdir.as_ref().join(filename.as_str());
let path = joined.as_path();
ab.add_archive(path, Box::new(|_| false)).unwrap();
ab.add_archive(path, Box::new(|_, _| false)).unwrap();
}
all_native_libs.extend(crate_info.native_libraries[&cnum].iter().cloned());
@@ -3146,7 +3156,6 @@ fn add_static_crate(
let bundled_lib_file_names = bundled_lib_file_names.clone();
sess.prof.generic_activity_with_arg("link_altering_rlib", name).run(|| {
let canonical_name = name.replace('-', "_");
let upstream_rust_objects_already_included =
are_upstream_rust_objects_already_included(sess);
let is_builtins = sess.target.no_builtins || !crate_info.is_no_builtins.contains(&cnum);
@@ -3154,15 +3163,13 @@ fn add_static_crate(
let mut archive = archive_builder_builder.new_archive_builder(sess);
if let Err(error) = archive.add_archive(
cratepath,
Box::new(move |f| {
if f == METADATA_FILENAME {
Box::new(move |f, digest| {
if f == METADATA_FILENAME || f == rmeta_link::FILENAME {
return true;
}
let canonical = f.replace('-', "_");
let is_rust_object =
canonical.starts_with(&canonical_name) && looks_like_rust_object_file(f);
digest.is_some_and(|d| d.rust_object_files.iter().any(|rf| rf == f));
// If we're performing LTO and this is a rust-generated object
// file, then we don't need the object file as it's part of the
@@ -9,6 +9,7 @@
pub(crate) mod linker;
pub mod lto;
pub mod metadata;
pub mod rmeta_link;
pub(crate) mod rpath;
pub mod symbol_export;
pub mod write;
@@ -0,0 +1,56 @@
//! Late-metadata archive member that lists which rlib entries are Rust object files,
//! and potentially other data collected and used when building or linking a rlib.
//! See <https://github.com/rust-lang/rust/issues/138243>.
use std::path::Path;
use object::read::archive::ArchiveFile;
use rustc_serialize::opaque::mem_encoder::MemEncoder;
use rustc_serialize::opaque::{MAGIC_END_BYTES, MemDecoder};
use rustc_serialize::{Decodable, Encodable};
use super::metadata::search_for_section;
pub(crate) const FILENAME: &str = "lib.rmeta-link";
pub(crate) const SECTION: &str = ".rmeta-link";
pub struct RmetaLink {
pub rust_object_files: Vec<String>,
}
impl RmetaLink {
pub(crate) fn encode(&self) -> Vec<u8> {
let mut encoder = MemEncoder::new();
self.rust_object_files.encode(&mut encoder);
let mut data = encoder.finish();
data.extend_from_slice(MAGIC_END_BYTES);
data
}
pub(crate) fn decode(data: &[u8]) -> Option<RmetaLink> {
let mut decoder = MemDecoder::new(data, 0).ok()?;
let rust_object_files = Vec::<String>::decode(&mut decoder);
Some(RmetaLink { rust_object_files })
}
}
/// Reads the digest from an already-parsed archive.
pub fn read(archive: &ArchiveFile<'_>, archive_data: &[u8], rlib_path: &Path) -> Option<RmetaLink> {
for entry in archive.members() {
let entry = entry.ok()?;
if entry.name() == FILENAME.as_bytes() {
let data = entry.data(archive_data).ok()?;
let section_data = search_for_section(rlib_path, data, SECTION).ok()?;
return RmetaLink::decode(section_data);
}
}
None
}
/// Like [`read`], but parses the archive from raw bytes.
///
/// Use this when the caller's `ArchiveFile` comes from a different version of the `object` crate.
pub fn read_from_data(archive_data: &[u8], rlib_path: &Path) -> Option<RmetaLink> {
let archive = ArchiveFile::parse(archive_data).ok()?;
read(&archive, archive_data, rlib_path)
}
+1 -18
View File
@@ -35,7 +35,7 @@
use rustc_serialize::opaque::{FileEncoder, MemDecoder};
use rustc_serialize::{Decodable, Decoder, Encodable, Encoder};
use rustc_session::Session;
use rustc_session::config::{CrateType, OutputFilenames, OutputType, RUST_CGU_EXT};
use rustc_session::config::{CrateType, OutputFilenames, OutputType};
use rustc_session::cstore::{self, CrateSource};
use rustc_session::lint::builtin::LINKER_MESSAGES;
use rustc_span::Symbol;
@@ -272,23 +272,6 @@ pub fn provide(providers: &mut Providers) {
providers.queries.global_backend_features = |_tcx: TyCtxt<'_>, ()| vec![];
}
/// Checks if the given filename ends with the `.rcgu.o` extension that `rustc`
/// uses for the object files it generates.
pub fn looks_like_rust_object_file(filename: &str) -> bool {
let path = Path::new(filename);
let ext = path.extension().and_then(|s| s.to_str());
if ext != Some(OutputType::Object.extension()) {
// The file name does not end with ".o", so it can't be an object file.
return false;
}
// Strip the ".o" at the end
let ext2 = path.file_stem().and_then(|s| Path::new(s).extension()).and_then(|s| s.to_str());
// Check if the "inner" extension
ext2 == Some(RUST_CGU_EXT)
}
const RLINK_VERSION: u32 = 1;
const RLINK_MAGIC: &[u8] = b"rustlink";