Skip to content

Commit 8fb65ac

Browse files
committed
Auto merge of rust-lang#120855 - bjorn3:split_metadata4, r=<try>
Introduce -Zsplit-metadata option This will split the crate metadata out of library files. Instead only the svh and a bit of extra metadata is preserved to allow for loading the right rmeta file. This significantly reduces library size. In addition it allows for cheaper checks if different library files are the same crate. A fair amount of the complexity in this PR is to work around the fact that cargo doesn't directly support this option yet. Fixes rust-lang#23366 Fixes rust-lang#57076 Revives rust-lang#93945
2 parents e28fae5 + 7770002 commit 8fb65ac

File tree

22 files changed

+212
-120
lines changed

22 files changed

+212
-120
lines changed

compiler/rustc_codegen_cranelift/build_system/tests.rs

+1
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,7 @@ impl<'a> TestRunner<'a> {
475475
cmd.arg("-Zunstable-options");
476476
cmd.arg("--check-cfg=cfg(no_unstable_features)");
477477
cmd.arg("--check-cfg=cfg(jit)");
478+
cmd.arg("--emit=metadata,link");
478479
cmd.args(args);
479480
cmd
480481
}

compiler/rustc_codegen_ssa/src/back/link.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -316,8 +316,11 @@ fn link_rlib<'a>(
316316

317317
let trailing_metadata = match flavor {
318318
RlibFlavor::Normal => {
319-
let (metadata, metadata_position) =
320-
create_wrapper_file(sess, b".rmeta".to_vec(), codegen_results.metadata.raw_data());
319+
let (metadata, metadata_position) = create_wrapper_file(
320+
sess,
321+
b".rmeta".to_vec(),
322+
codegen_results.metadata.maybe_reference(),
323+
);
321324
let metadata = emit_wrapper_file(sess, &metadata, tmpdir, METADATA_FILENAME);
322325
match metadata_position {
323326
MetadataPosition::First => {

compiler/rustc_codegen_ssa/src/back/metadata.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -528,8 +528,8 @@ pub fn create_compressed_metadata_file(
528528
symbol_name: &str,
529529
) -> Vec<u8> {
530530
let mut packed_metadata = rustc_metadata::METADATA_HEADER.to_vec();
531-
packed_metadata.write_all(&(metadata.raw_data().len() as u64).to_le_bytes()).unwrap();
532-
packed_metadata.extend(metadata.raw_data());
531+
packed_metadata.write_all(&(metadata.maybe_reference().len() as u64).to_le_bytes()).unwrap();
532+
packed_metadata.extend(metadata.maybe_reference());
533533

534534
let Some(mut file) = create_object_file(sess) else {
535535
return packed_metadata.to_vec();

compiler/rustc_driver_impl/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,7 @@ fn list_metadata(early_dcx: &EarlyDiagCtxt, sess: &Session, metadata_loader: &dy
677677
metadata_loader,
678678
&mut v,
679679
&sess.opts.unstable_opts.ls,
680+
sess.cfg_version,
680681
)
681682
.unwrap();
682683
safe_println!("{}", String::from_utf8(v).unwrap());

compiler/rustc_interface/src/tests.rs

+1
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,7 @@ fn test_unstable_options_tracking_hash() {
702702
untracked!(shell_argfiles, true);
703703
untracked!(span_debug, true);
704704
untracked!(span_free_formats, true);
705+
untracked!(split_metadata, true);
705706
untracked!(temps_dir, Some(String::from("abc")));
706707
untracked!(threads, 99);
707708
untracked!(time_llvm_passes, true);

compiler/rustc_metadata/src/fs.rs

+10-5
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ pub fn encode_and_write_metadata(tcx: TyCtxt<'_>) -> (EncodedMetadata, bool) {
5050
.tempdir_in(out_filename.parent().unwrap_or_else(|| Path::new("")))
5151
.unwrap_or_else(|err| tcx.dcx().emit_fatal(FailedCreateTempdir { err }));
5252
let metadata_tmpdir = MaybeTempDir::new(metadata_tmpdir, tcx.sess.opts.cg.save_temps);
53-
let metadata_filename = metadata_tmpdir.as_ref().join(METADATA_FILENAME);
53+
let metadata_filename = metadata_tmpdir.as_ref().join("full.rmeta");
54+
let metadata_reference_filename = metadata_tmpdir.as_ref().join("ref.rmeta");
5455

5556
// Always create a file at `metadata_filename`, even if we have nothing to write to it.
5657
// This simplifies the creation of the output `out_filename` when requested.
@@ -60,9 +61,12 @@ pub fn encode_and_write_metadata(tcx: TyCtxt<'_>) -> (EncodedMetadata, bool) {
6061
std::fs::File::create(&metadata_filename).unwrap_or_else(|err| {
6162
tcx.dcx().emit_fatal(FailedCreateFile { filename: &metadata_filename, err });
6263
});
64+
std::fs::File::create(&metadata_reference_filename).unwrap_or_else(|err| {
65+
tcx.dcx().emit_fatal(FailedCreateFile { filename: &metadata_filename, err });
66+
});
6367
}
6468
MetadataKind::Uncompressed | MetadataKind::Compressed => {
65-
encode_metadata(tcx, &metadata_filename);
69+
encode_metadata(tcx, &metadata_filename, &metadata_reference_filename)
6670
}
6771
};
6872

@@ -100,9 +104,10 @@ pub fn encode_and_write_metadata(tcx: TyCtxt<'_>) -> (EncodedMetadata, bool) {
100104

101105
// Load metadata back to memory: codegen may need to include it in object files.
102106
let metadata =
103-
EncodedMetadata::from_path(metadata_filename, metadata_tmpdir).unwrap_or_else(|err| {
104-
tcx.dcx().emit_fatal(FailedCreateEncodedMetadata { err });
105-
});
107+
EncodedMetadata::from_path(metadata_filename, metadata_reference_filename, metadata_tmpdir)
108+
.unwrap_or_else(|err| {
109+
tcx.dcx().emit_fatal(FailedCreateEncodedMetadata { err });
110+
});
106111

107112
let need_metadata_module = metadata_kind == MetadataKind::Compressed;
108113

compiler/rustc_metadata/src/locator.rs

+62-69
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,6 @@ use std::{cmp, fmt};
243243
pub(crate) struct CrateLocator<'a> {
244244
// Immutable per-session configuration.
245245
only_needs_metadata: bool,
246-
sysroot: &'a Path,
247246
metadata_loader: &'a dyn MetadataLoader,
248247
cfg_version: &'static str,
249248

@@ -319,7 +318,6 @@ impl<'a> CrateLocator<'a> {
319318

320319
CrateLocator {
321320
only_needs_metadata,
322-
sysroot: &sess.sysroot,
323321
metadata_loader,
324322
cfg_version: sess.cfg_version,
325323
crate_name,
@@ -569,31 +567,47 @@ impl<'a> CrateLocator<'a> {
569567
debug!("skipping empty file");
570568
continue;
571569
}
572-
let (hash, metadata) =
573-
match get_metadata_section(self.target, flavor, &lib, self.metadata_loader) {
574-
Ok(blob) => {
575-
if let Some(h) = self.crate_matches(&blob, &lib) {
576-
(h, blob)
577-
} else {
578-
info!("metadata mismatch");
579-
continue;
580-
}
581-
}
582-
Err(MetadataError::LoadFailure(err)) => {
583-
info!("no metadata found: {}", err);
584-
// The file was present and created by the same compiler version, but we
585-
// couldn't load it for some reason. Give a hard error instead of silently
586-
// ignoring it, but only if we would have given an error anyway.
587-
self.crate_rejections
588-
.via_invalid
589-
.push(CrateMismatch { path: lib, got: err });
590-
continue;
591-
}
592-
Err(err @ MetadataError::NotPresent(_)) => {
593-
info!("no metadata found: {}", err);
570+
let (hash, metadata) = match get_metadata_section(
571+
self.target,
572+
flavor,
573+
&lib,
574+
self.metadata_loader,
575+
self.cfg_version,
576+
) {
577+
Ok(blob) => {
578+
if let Some(h) = self.crate_matches(&blob, &lib) {
579+
(h, blob)
580+
} else {
581+
info!("metadata mismatch");
594582
continue;
595583
}
596-
};
584+
}
585+
Err(MetadataError::VersionMismatch { expected_version, found_version }) => {
586+
// The file was present and created by the same compiler version, but we
587+
// couldn't load it for some reason. Give a hard error instead of silently
588+
// ignoring it, but only if we would have given an error anyway.
589+
info!(
590+
"Rejecting via version: expected {} got {}",
591+
expected_version, found_version
592+
);
593+
self.crate_rejections
594+
.via_version
595+
.push(CrateMismatch { path: lib, got: found_version });
596+
continue;
597+
}
598+
Err(MetadataError::LoadFailure(err)) => {
599+
info!("no metadata found: {}", err);
600+
// The file was present and created by the same compiler version, but we
601+
// couldn't load it for some reason. Give a hard error instead of silently
602+
// ignoring it, but only if we would have given an error anyway.
603+
self.crate_rejections.via_invalid.push(CrateMismatch { path: lib, got: err });
604+
continue;
605+
}
606+
Err(err @ MetadataError::NotPresent(_)) => {
607+
info!("no metadata found: {}", err);
608+
continue;
609+
}
610+
};
597611
// If we see multiple hashes, emit an error about duplicate candidates.
598612
if slot.as_ref().is_some_and(|s| s.0 != hash) {
599613
if let Some(candidates) = err_data {
@@ -610,33 +624,11 @@ impl<'a> CrateLocator<'a> {
610624
continue;
611625
}
612626

613-
// Ok so at this point we've determined that `(lib, kind)` above is
614-
// a candidate crate to load, and that `slot` is either none (this
615-
// is the first crate of its kind) or if some the previous path has
616-
// the exact same hash (e.g., it's the exact same crate).
617-
//
618-
// In principle these two candidate crates are exactly the same so
619-
// we can choose either of them to link. As a stupidly gross hack,
620-
// however, we favor crate in the sysroot.
621-
//
622-
// You can find more info in rust-lang/rust#39518 and various linked
623-
// issues, but the general gist is that during testing libstd the
624-
// compilers has two candidates to choose from: one in the sysroot
625-
// and one in the deps folder. These two crates are the exact same
626-
// crate but if the compiler chooses the one in the deps folder
627-
// it'll cause spurious errors on Windows.
628-
//
629-
// As a result, we favor the sysroot crate here. Note that the
630-
// candidates are all canonicalized, so we canonicalize the sysroot
631-
// as well.
632-
if let Some((prev, _)) = &ret {
633-
let sysroot = self.sysroot;
634-
let sysroot = try_canonicalize(sysroot).unwrap_or_else(|_| sysroot.to_path_buf());
635-
if prev.starts_with(&sysroot) {
636-
continue;
637-
}
627+
if !metadata.get_header().is_reference {
628+
// FIXME nicer error when only an rlib or dylib with is_reference is found
629+
// and no .rmeta?
630+
*slot = Some((hash, metadata, lib.clone()));
638631
}
639-
*slot = Some((hash, metadata, lib.clone()));
640632
ret = Some((lib, kind));
641633
}
642634

@@ -648,16 +640,6 @@ impl<'a> CrateLocator<'a> {
648640
}
649641

650642
fn crate_matches(&mut self, metadata: &MetadataBlob, libpath: &Path) -> Option<Svh> {
651-
let rustc_version = rustc_version(self.cfg_version);
652-
let found_version = metadata.get_rustc_version();
653-
if found_version != rustc_version {
654-
info!("Rejecting via version: expected {} got {}", rustc_version, found_version);
655-
self.crate_rejections
656-
.via_version
657-
.push(CrateMismatch { path: libpath.to_path_buf(), got: found_version });
658-
return None;
659-
}
660-
661643
let header = metadata.get_header();
662644
if header.is_proc_macro_crate != self.is_proc_macro {
663645
info!(
@@ -736,10 +718,12 @@ impl<'a> CrateLocator<'a> {
736718
let loc_canon = loc.canonicalized().clone();
737719
let loc = loc.original();
738720
if loc.file_name().unwrap().to_str().unwrap().ends_with(".rlib") {
721+
rmetas.insert(loc_canon.with_extension("rmeta"), PathKind::ExternFlag);
739722
rlibs.insert(loc_canon, PathKind::ExternFlag);
740723
} else if loc.file_name().unwrap().to_str().unwrap().ends_with(".rmeta") {
741724
rmetas.insert(loc_canon, PathKind::ExternFlag);
742725
} else {
726+
rmetas.insert(loc_canon.with_extension("rmeta"), PathKind::ExternFlag);
743727
dylibs.insert(loc_canon, PathKind::ExternFlag);
744728
}
745729
} else {
@@ -770,6 +754,7 @@ fn get_metadata_section<'p>(
770754
flavor: CrateFlavor,
771755
filename: &'p Path,
772756
loader: &dyn MetadataLoader,
757+
cfg_version: &'static str,
773758
) -> Result<MetadataBlob, MetadataError<'p>> {
774759
if !filename.exists() {
775760
return Err(MetadataError::NotPresent(filename));
@@ -847,13 +832,12 @@ fn get_metadata_section<'p>(
847832
}
848833
};
849834
let blob = MetadataBlob(raw_bytes);
850-
if blob.is_compatible() {
851-
Ok(blob)
852-
} else {
853-
Err(MetadataError::LoadFailure(format!(
854-
"invalid metadata version found: {}",
855-
filename.display()
856-
)))
835+
match blob.check_compatibility(cfg_version) {
836+
Ok(()) => Ok(blob),
837+
Err(version) => Err(MetadataError::VersionMismatch {
838+
expected_version: cfg_version,
839+
found_version: version,
840+
}),
857841
}
858842
}
859843

@@ -864,9 +848,10 @@ pub fn list_file_metadata(
864848
metadata_loader: &dyn MetadataLoader,
865849
out: &mut dyn Write,
866850
ls_kinds: &[String],
851+
cfg_version: &'static str,
867852
) -> IoResult<()> {
868853
let flavor = get_flavor_from_path(path);
869-
match get_metadata_section(target, flavor, path, metadata_loader) {
854+
match get_metadata_section(target, flavor, path, metadata_loader, cfg_version) {
870855
Ok(metadata) => metadata.list_crate_metadata(out, ls_kinds),
871856
Err(msg) => write!(out, "{msg}\n"),
872857
}
@@ -932,6 +917,8 @@ enum MetadataError<'a> {
932917
NotPresent(&'a Path),
933918
/// The file was present and invalid.
934919
LoadFailure(String),
920+
/// The file was present, but compiled with a different rustc version.
921+
VersionMismatch { expected_version: &'static str, found_version: String },
935922
}
936923

937924
impl fmt::Display for MetadataError<'_> {
@@ -941,6 +928,12 @@ impl fmt::Display for MetadataError<'_> {
941928
f.write_str(&format!("no such file: '{}'", filename.display()))
942929
}
943930
MetadataError::LoadFailure(msg) => f.write_str(msg),
931+
MetadataError::VersionMismatch { expected_version, found_version } => {
932+
f.write_str(&format!(
933+
"rustc version mismatch. expected {}, found {}",
934+
expected_version, found_version,
935+
))
936+
}
944937
}
945938
}
946939
}

compiler/rustc_metadata/src/rmeta/decoder.rs

+16-6
Original file line numberDiff line numberDiff line change
@@ -680,13 +680,23 @@ impl<'a, 'tcx, I: Idx, T> Decodable<DecodeContext<'a, 'tcx>> for LazyTable<I, T>
680680
implement_ty_decoder!(DecodeContext<'a, 'tcx>);
681681

682682
impl MetadataBlob {
683-
pub(crate) fn is_compatible(&self) -> bool {
684-
self.blob().starts_with(METADATA_HEADER)
685-
}
683+
pub(crate) fn check_compatibility(&self, cfg_version: &'static str) -> Result<(), String> {
684+
if !self.blob().starts_with(METADATA_HEADER) {
685+
if self.blob().starts_with(b"rust") {
686+
return Err("<unknown rustc version>".to_string());
687+
}
688+
return Err("<invalid metadata header>".to_string());
689+
}
686690

687-
pub(crate) fn get_rustc_version(&self) -> String {
688-
LazyValue::<String>::from_position(NonZeroUsize::new(METADATA_HEADER.len() + 8).unwrap())
689-
.decode(self)
691+
let found_version = LazyValue::<String>::from_position(
692+
NonZeroUsize::new(METADATA_HEADER.len() + 8).unwrap(),
693+
)
694+
.decode(self);
695+
if rustc_version(cfg_version) != found_version {
696+
return Err(found_version);
697+
}
698+
699+
Ok(())
690700
}
691701

692702
fn root_pos(&self) -> NonZeroUsize {

0 commit comments

Comments
 (0)