Skip to content

Commit 9bc8e6d

Browse files
committed
trans: Link rlibs to dylibs with --whole-archive
This commit starts passing the `--whole-archive` flag (`-force_load` on OSX) to the linker when linking rlibs into dylibs. The primary purpose of this commit is to ensure that the linker doesn't strip out objects from an archive when creating a dynamic library. Information on how this can go wrong can be found in issues #14344 and #25185. The unfortunate part about passing this flag to the linker is that we have to preprocess the rlib to remove the metadata and compressed bytecode found within. This means that creating a dylib will now take longer to link as we've got to copy around the input rlibs to a temporary location, modify them, and then invoke the linker. This isn't done for executables, however, so the "hello world" compile time is not affected. This fix was instigated because of the previous commit where rlibs may not contain multiple object files instead of one due to codegen units being greater than one. That change prevented the main distribution from being compiled with more than one codegen-unit and this commit fixes that. Closes #14344 Closes #25185
1 parent d23239b commit 9bc8e6d

File tree

16 files changed

+229
-129
lines changed

16 files changed

+229
-129
lines changed

src/liballoc/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,5 @@ pub fn oom() -> ! {
148148
// optimize it out).
149149
#[doc(hidden)]
150150
#[unstable(feature = "issue_14344_fixme")]
151+
#[cfg(stage0)]
151152
pub fn fixme_14344_be_sure_to_link_to_collections() {}

src/libcollections/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ pub mod btree_set {
138138
// FIXME(#14344) this shouldn't be necessary
139139
#[doc(hidden)]
140140
#[unstable(feature = "issue_14344_fixme")]
141+
#[cfg(stage0)]
141142
pub fn fixme_14344_be_sure_to_link_to_collections() {}
142143

143144
#[cfg(not(test))]

src/liblibc/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -6431,6 +6431,7 @@ pub mod funcs {
64316431
}
64326432

64336433
#[doc(hidden)]
6434+
#[cfg(stage0)]
64346435
pub fn issue_14344_workaround() {} // FIXME #14344 force linkage to happen correctly
64356436

64366437
#[test] fn work_on_windows() { } // FIXME #10872 needed for a happy windows

src/librustc/metadata/encoder.rs

+1-5
Original file line numberDiff line numberDiff line change
@@ -2136,11 +2136,7 @@ fn encode_metadata_inner(wr: &mut Cursor<Vec<u8>>,
21362136
let mut rbml_w = Encoder::new(wr);
21372137

21382138
encode_crate_name(&mut rbml_w, &ecx.link_meta.crate_name);
2139-
encode_crate_triple(&mut rbml_w,
2140-
&tcx.sess
2141-
.opts
2142-
.target_triple
2143-
);
2139+
encode_crate_triple(&mut rbml_w, &tcx.sess.opts.target_triple);
21442140
encode_hash(&mut rbml_w, &ecx.link_meta.crate_hash);
21452141
encode_dylib_dependency_formats(&mut rbml_w, &ecx);
21462142

src/librustc_driver/driver.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -804,8 +804,8 @@ fn write_out_deps(sess: &Session,
804804
match *output_type {
805805
config::OutputTypeExe => {
806806
for output in sess.crate_types.borrow().iter() {
807-
let p = link::filename_for_input(sess, *output,
808-
id, &file);
807+
let p = link::filename_for_input(sess, *output, id,
808+
outputs);
809809
out_filenames.push(p);
810810
}
811811
}

src/librustc_driver/lib.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -452,10 +452,8 @@ impl RustcDefaultCalls {
452452
let metadata = driver::collect_crate_metadata(sess, attrs);
453453
*sess.crate_metadata.borrow_mut() = metadata;
454454
for &style in &crate_types {
455-
let fname = link::filename_for_input(sess,
456-
style,
457-
&id,
458-
&t_outputs.with_extension(""));
455+
let fname = link::filename_for_input(sess, style, &id,
456+
&t_outputs);
459457
println!("{}", fname.file_name().unwrap()
460458
.to_string_lossy());
461459
}

src/librustc_trans/back/link.rs

+94-86
Original file line numberDiff line numberDiff line change
@@ -464,26 +464,25 @@ fn is_writeable(p: &Path) -> bool {
464464

465465
pub fn filename_for_input(sess: &Session,
466466
crate_type: config::CrateType,
467-
name: &str,
468-
out_filename: &Path) -> PathBuf {
469-
let libname = format!("{}{}", name, sess.opts.cg.extra_filename);
467+
crate_name: &str,
468+
outputs: &OutputFilenames) -> PathBuf {
469+
let libname = format!("{}{}", crate_name, sess.opts.cg.extra_filename);
470470
match crate_type {
471471
config::CrateTypeRlib => {
472-
out_filename.with_file_name(&format!("lib{}.rlib", libname))
472+
outputs.out_directory.join(&format!("lib{}.rlib", libname))
473473
}
474474
config::CrateTypeDylib => {
475475
let (prefix, suffix) = (&sess.target.target.options.dll_prefix,
476476
&sess.target.target.options.dll_suffix);
477-
out_filename.with_file_name(&format!("{}{}{}",
478-
prefix,
479-
libname,
480-
suffix))
477+
outputs.out_directory.join(&format!("{}{}{}", prefix, libname,
478+
suffix))
481479
}
482480
config::CrateTypeStaticlib => {
483-
out_filename.with_file_name(&format!("lib{}.a", libname))
481+
outputs.out_directory.join(&format!("lib{}.a", libname))
484482
}
485483
config::CrateTypeExecutable => {
486484
let suffix = &sess.target.target.options.exe_suffix;
485+
let out_filename = outputs.path(OutputTypeExe);
487486
if suffix.is_empty() {
488487
out_filename.to_path_buf()
489488
} else {
@@ -501,10 +500,7 @@ fn link_binary_output(sess: &Session,
501500
let objects = object_filenames(sess, outputs);
502501
let out_filename = match outputs.single_output_file {
503502
Some(ref file) => file.clone(),
504-
None => {
505-
let out_filename = outputs.path(OutputTypeExe);
506-
filename_for_input(sess, crate_type, crate_name, &out_filename)
507-
}
503+
None => filename_for_input(sess, crate_type, crate_name, outputs),
508504
};
509505

510506
// Make sure files are writeable. Mac, FreeBSD, and Windows system linkers
@@ -551,6 +547,19 @@ fn archive_search_paths(sess: &Session) -> Vec<PathBuf> {
551547
return search;
552548
}
553549

550+
fn archive_config<'a>(sess: &'a Session,
551+
output: &Path) -> ArchiveConfig<'a> {
552+
ArchiveConfig {
553+
handler: &sess.diagnostic().handler,
554+
dst: output.to_path_buf(),
555+
lib_search_paths: archive_search_paths(sess),
556+
slib_prefix: sess.target.target.options.staticlib_prefix.clone(),
557+
slib_suffix: sess.target.target.options.staticlib_suffix.clone(),
558+
ar_prog: get_ar_prog(sess),
559+
command_path: command_path(sess),
560+
}
561+
}
562+
554563
// Create an 'rlib'
555564
//
556565
// An rlib in its current incarnation is essentially a renamed .a file. The
@@ -562,17 +571,7 @@ fn link_rlib<'a>(sess: &'a Session,
562571
objects: &[PathBuf],
563572
out_filename: &Path) -> ArchiveBuilder<'a> {
564573
info!("preparing rlib from {:?} to {:?}", objects, out_filename);
565-
let handler = &sess.diagnostic().handler;
566-
let config = ArchiveConfig {
567-
handler: handler,
568-
dst: out_filename.to_path_buf(),
569-
lib_search_paths: archive_search_paths(sess),
570-
slib_prefix: sess.target.target.options.staticlib_prefix.clone(),
571-
slib_suffix: sess.target.target.options.staticlib_suffix.clone(),
572-
ar_prog: get_ar_prog(sess),
573-
command_path: command_path(sess),
574-
};
575-
let mut ab = ArchiveBuilder::create(config);
574+
let mut ab = ArchiveBuilder::create(archive_config(sess, out_filename));
576575
for obj in objects {
577576
ab.add_file(obj).unwrap();
578577
}
@@ -1131,7 +1130,7 @@ fn add_upstream_rust_crates(cmd: &mut Linker, sess: &Session,
11311130
add_dynamic_crate(cmd, sess, &src.dylib.unwrap().0)
11321131
}
11331132
cstore::RequireStatic => {
1134-
add_static_crate(cmd, sess, tmpdir, &src.rlib.unwrap().0)
1133+
add_static_crate(cmd, sess, tmpdir, dylib, &src.rlib.unwrap().0)
11351134
}
11361135
}
11371136

@@ -1147,71 +1146,80 @@ fn add_upstream_rust_crates(cmd: &mut Linker, sess: &Session,
11471146
}
11481147

11491148
// Adds the static "rlib" versions of all crates to the command line.
1149+
// There's a bit of magic which happens here specifically related to LTO and
1150+
// dynamic libraries. Specifically:
1151+
//
1152+
// * For LTO, we remove upstream object files.
1153+
// * For dylibs we remove metadata and bytecode from upstream rlibs
1154+
//
1155+
// When performing LTO, all of the bytecode from the upstream libraries has
1156+
// already been included in our object file output. As a result we need to
1157+
// remove the object files in the upstream libraries so the linker doesn't
1158+
// try to include them twice (or whine about duplicate symbols). We must
1159+
// continue to include the rest of the rlib, however, as it may contain
1160+
// static native libraries which must be linked in.
1161+
//
1162+
// When making a dynamic library, linkers by default don't include any
1163+
// object files in an archive if they're not necessary to resolve the link.
1164+
// We basically want to convert the archive (rlib) to a dylib, though, so we
1165+
// *do* want everything included in the output, regardless of whether the
1166+
// linker thinks it's needed or not. As a result we must use the
1167+
// --whole-archive option (or the platform equivalent). When using this
1168+
// option the linker will fail if there are non-objects in the archive (such
1169+
// as our own metadata and/or bytecode). All in all, for rlibs to be
1170+
// entirely included in dylibs, we need to remove all non-object files.
1171+
//
1172+
// Note, however, that if we're not doing LTO or we're not producing a dylib
1173+
// (aka we're making an executable), we can just pass the rlib blindly to
1174+
// the linker (fast) because it's fine if it's not actually included as
1175+
// we're at the end of the dependency chain.
11501176
fn add_static_crate(cmd: &mut Linker, sess: &Session, tmpdir: &Path,
1151-
cratepath: &Path) {
1152-
// When performing LTO on an executable output, all of the
1153-
// bytecode from the upstream libraries has already been
1154-
// included in our object file output. We need to modify all of
1155-
// the upstream archives to remove their corresponding object
1156-
// file to make sure we don't pull the same code in twice.
1157-
//
1158-
// We must continue to link to the upstream archives to be sure
1159-
// to pull in native static dependencies. As the final caveat,
1160-
// on Linux it is apparently illegal to link to a blank archive,
1161-
// so if an archive no longer has any object files in it after
1162-
// we remove `lib.o`, then don't link against it at all.
1163-
//
1164-
// If we're not doing LTO, then our job is simply to just link
1165-
// against the archive.
1166-
if sess.lto() {
1167-
let name = cratepath.file_name().unwrap().to_str().unwrap();
1168-
let name = &name[3..name.len() - 5]; // chop off lib/.rlib
1169-
time(sess.time_passes(),
1170-
&format!("altering {}.rlib", name),
1171-
(), |()| {
1172-
let dst = tmpdir.join(cratepath.file_name().unwrap());
1173-
match fs::copy(&cratepath, &dst) {
1174-
Ok(..) => {}
1175-
Err(e) => {
1176-
sess.fatal(&format!("failed to copy {} to {}: {}",
1177-
cratepath.display(),
1178-
dst.display(), e));
1179-
}
1177+
dylib: bool, cratepath: &Path) {
1178+
if !sess.lto() && !dylib {
1179+
cmd.link_rlib(&fix_windows_verbatim_for_gcc(cratepath));
1180+
return
1181+
}
1182+
1183+
let dst = tmpdir.join(cratepath.file_name().unwrap());
1184+
let name = cratepath.file_name().unwrap().to_str().unwrap();
1185+
let name = &name[3..name.len() - 5]; // chop off lib/.rlib
1186+
1187+
time(sess.time_passes(), &format!("altering {}.rlib", name), (), |()| {
1188+
let err = (|| {
1189+
io::copy(&mut try!(fs::File::open(&cratepath)),
1190+
&mut try!(fs::File::create(&dst)))
1191+
})();
1192+
if let Err(e) = err {
1193+
sess.fatal(&format!("failed to copy {} to {}: {}",
1194+
cratepath.display(), dst.display(), e));
1195+
}
1196+
1197+
let mut archive = Archive::open(archive_config(sess, &dst));
1198+
archive.remove_file(METADATA_FILENAME);
1199+
1200+
let mut any_objects = false;
1201+
for f in archive.files() {
1202+
if f.ends_with("bytecode.deflate") {
1203+
archive.remove_file(&f);
1204+
continue
11801205
}
1181-
// Fix up permissions of the copy, as fs::copy() preserves
1182-
// permissions, but the original file may have been installed
1183-
// by a package manager and may be read-only.
1184-
match fs::metadata(&dst).and_then(|m| {
1185-
let mut perms = m.permissions();
1186-
perms.set_readonly(false);
1187-
fs::set_permissions(&dst, perms)
1188-
}) {
1189-
Ok(..) => {}
1190-
Err(e) => {
1191-
sess.fatal(&format!("failed to chmod {} when preparing \
1192-
for LTO: {}", dst.display(), e));
1206+
let canonical = f.replace("-", "_");
1207+
let canonical_name = name.replace("-", "_");
1208+
if sess.lto() && canonical.starts_with(&canonical_name) &&
1209+
canonical.ends_with(".o") {
1210+
let num = &f[name.len()..f.len() - 2];
1211+
if num.len() > 0 && num[1..].parse::<u32>().is_ok() {
1212+
archive.remove_file(&f);
1213+
continue
11931214
}
11941215
}
1195-
let handler = &sess.diagnostic().handler;
1196-
let config = ArchiveConfig {
1197-
handler: handler,
1198-
dst: dst.clone(),
1199-
lib_search_paths: archive_search_paths(sess),
1200-
slib_prefix: sess.target.target.options.staticlib_prefix.clone(),
1201-
slib_suffix: sess.target.target.options.staticlib_suffix.clone(),
1202-
ar_prog: get_ar_prog(sess),
1203-
command_path: command_path(sess),
1204-
};
1205-
let mut archive = Archive::open(config);
1206-
archive.remove_file(&format!("{}.o", name));
1207-
let files = archive.files();
1208-
if files.iter().any(|s| s.ends_with(".o")) {
1209-
cmd.link_rlib(&dst);
1210-
}
1211-
});
1212-
} else {
1213-
cmd.link_rlib(&fix_windows_verbatim_for_gcc(cratepath));
1214-
}
1216+
any_objects = true;
1217+
}
1218+
1219+
if any_objects {
1220+
cmd.link_whole_rlib(&fix_windows_verbatim_for_gcc(&dst));
1221+
}
1222+
});
12151223
}
12161224

12171225
// Same thing as above, but for dynamic crates instead of static crates.

src/librustc_trans/back/linker.rs

+16
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ pub trait Linker {
3030
fn link_framework(&mut self, framework: &str);
3131
fn link_staticlib(&mut self, lib: &str);
3232
fn link_rlib(&mut self, lib: &Path);
33+
fn link_whole_rlib(&mut self, lib: &Path);
3334
fn link_whole_staticlib(&mut self, lib: &str, search_path: &[PathBuf]);
3435
fn include_path(&mut self, path: &Path);
3536
fn framework_path(&mut self, path: &Path);
@@ -96,6 +97,17 @@ impl<'a> Linker for GnuLinker<'a> {
9697
}
9798
}
9899

100+
fn link_whole_rlib(&mut self, lib: &Path) {
101+
if self.sess.target.target.options.is_like_osx {
102+
let mut v = OsString::from("-Wl,-force_load,");
103+
v.push(lib);
104+
self.cmd.arg(&v);
105+
} else {
106+
self.cmd.arg("-Wl,--whole-archive").arg(lib)
107+
.arg("-Wl,--no-whole-archive");
108+
}
109+
}
110+
99111
fn gc_sections(&mut self, is_dylib: bool) {
100112
// The dead_strip option to the linker specifies that functions and data
101113
// unreachable by the entry point will be removed. This is quite useful
@@ -250,6 +262,10 @@ impl<'a> Linker for MsvcLinker<'a> {
250262
// not supported?
251263
self.link_staticlib(lib);
252264
}
265+
fn link_whole_rlib(&mut self, path: &Path) {
266+
// not supported?
267+
self.link_rlib(path);
268+
}
253269
fn optimize(&mut self) {
254270
// Needs more investigation of `/OPT` arguments
255271
}

src/librustc_trans/back/lto.rs

+9-30
Original file line numberDiff line numberDiff line change
@@ -56,33 +56,14 @@ pub fn run(sess: &session::Session, llmod: ModuleRef,
5656
};
5757

5858
let archive = ArchiveRO::open(&path).expect("wanted an rlib");
59-
let file = path.file_name().unwrap().to_str().unwrap();
60-
let file = &file[3..file.len() - 5]; // chop off lib/.rlib
61-
debug!("reading {}", file);
62-
for i in 0.. {
63-
let filename = format!("{}.{}.bytecode.deflate", file, i);
64-
let msg = format!("check for {}", filename);
65-
let bc_encoded = time(sess.time_passes(), &msg, (), |_| {
66-
archive.iter().find(|section| {
67-
section.name() == Some(&filename[..])
68-
})
69-
});
70-
let bc_encoded = match bc_encoded {
71-
Some(data) => data,
72-
None => {
73-
if i == 0 {
74-
// No bitcode was found at all.
75-
sess.fatal(&format!("missing compressed bytecode in {}",
76-
path.display()));
77-
}
78-
// No more bitcode files to read.
79-
break
80-
}
81-
};
82-
let bc_encoded = bc_encoded.data();
59+
let bytecodes = archive.iter().filter_map(|child| {
60+
child.name().map(|name| (name, child))
61+
}).filter(|&(name, _)| name.ends_with("bytecode.deflate"));
62+
for (name, data) in bytecodes {
63+
let bc_encoded = data.data();
8364

8465
let bc_decoded = if is_versioned_bytecode_format(bc_encoded) {
85-
time(sess.time_passes(), &format!("decode {}.{}.bc", file, i), (), |_| {
66+
time(sess.time_passes(), &format!("decode {}", name), (), |_| {
8667
// Read the version
8768
let version = extract_bytecode_format_version(bc_encoded);
8869

@@ -106,7 +87,7 @@ pub fn run(sess: &session::Session, llmod: ModuleRef,
10687
}
10788
})
10889
} else {
109-
time(sess.time_passes(), &format!("decode {}.{}.bc", file, i), (), |_| {
90+
time(sess.time_passes(), &format!("decode {}", name), (), |_| {
11091
// the object must be in the old, pre-versioning format, so simply
11192
// inflate everything and let LLVM decide if it can make sense of it
11293
match flate::inflate_bytes(bc_encoded) {
@@ -120,10 +101,8 @@ pub fn run(sess: &session::Session, llmod: ModuleRef,
120101
};
121102

122103
let ptr = bc_decoded.as_ptr();
123-
debug!("linking {}, part {}", name, i);
124-
time(sess.time_passes(),
125-
&format!("ll link {}.{}", name, i),
126-
(),
104+
debug!("linking {}", name);
105+
time(sess.time_passes(), &format!("ll link {}", name), (),
127106
|()| unsafe {
128107
if !llvm::LLVMRustLinkInExternalBitcode(llmod,
129108
ptr as *const libc::c_char,

0 commit comments

Comments
 (0)