rustc_trans: Abstract linker support behind a trait

This trait will be used to correctly build a command line for link.exe with MSVC
and may perhaps one day be used to generate a command line for `lld`, but this
commit currently just refactors the bindings used to call `ld`.
This commit is contained in:
Alex Crichton 2015-05-08 15:31:23 -07:00
parent 4cc025d83c
commit 2d5e5777fd
3 changed files with 237 additions and 142 deletions

View file

@ -9,9 +9,9 @@
// except according to those terms.
use super::archive::{Archive, ArchiveBuilder, ArchiveConfig, METADATA_FILENAME};
use super::archive;
use super::rpath;
use super::linker::{Linker, GnuLinker};
use super::rpath::RPathConfig;
use super::rpath;
use super::svh::Svh;
use session::config;
use session::config::NoDebugInfo;
@ -29,7 +29,6 @@ use util::sha2::{Digest, Sha256};
use util::fs::fix_windows_verbatim_for_gcc;
use rustc_back::tempdir::TempDir;
use std::ffi::OsString;
use std::fs::{self, PathExt};
use std::io::{self, Read, Write};
use std::mem;
@ -801,10 +800,13 @@ fn link_natively(sess: &Session, trans: &CrateTranslation, dylib: bool,
cmd.arg(root.join(obj));
}
link_args(&mut cmd, sess, dylib, tmpdir.path(),
trans, obj_filename, out_filename);
if !sess.target.target.options.no_compiler_rt {
cmd.arg("-lcompiler-rt");
{
let mut linker = GnuLinker { cmd: &mut cmd, sess: &sess };
link_args(&mut linker, sess, dylib, tmpdir.path(),
trans, obj_filename, out_filename);
if !sess.target.target.options.no_compiler_rt {
linker.link_staticlib("compiler-rt");
}
}
for obj in &sess.target.target.options.post_link_objects {
cmd.arg(root.join(obj));
@ -858,7 +860,7 @@ fn link_natively(sess: &Session, trans: &CrateTranslation, dylib: bool,
}
}
fn link_args(cmd: &mut Command,
fn link_args(cmd: &mut Linker,
sess: &Session,
dylib: bool,
tmpdir: &Path,
@ -873,10 +875,10 @@ fn link_args(cmd: &mut Command,
// target descriptor
let t = &sess.target.target;
cmd.arg("-L").arg(&fix_windows_verbatim_for_gcc(&lib_path));
cmd.arg("-o").arg(out_filename).arg(obj_filename);
cmd.include_path(&fix_windows_verbatim_for_gcc(&lib_path));
cmd.output_filename(out_filename);
cmd.add_object(obj_filename);
// Stack growth requires statically linking a __morestack function. Note
// that this is listed *before* all other libraries. Due to the usage of the
@ -895,89 +897,44 @@ fn link_args(cmd: &mut Command,
// will include the __morestack symbol 100% of the time, always resolving
// references to it even if the object above didn't use it.
if t.options.morestack {
if t.options.is_like_osx {
let morestack = lib_path.join("libmorestack.a");
let mut v = OsString::from("-Wl,-force_load,");
v.push(&morestack);
cmd.arg(&v);
} else {
cmd.args(&["-Wl,--whole-archive", "-lmorestack", "-Wl,--no-whole-archive"]);
}
cmd.link_whole_staticlib("morestack", &[lib_path]);
}
// When linking a dynamic library, we put the metadata into a section of the
// executable. This metadata is in a separate object file from the main
// object file, so we link that in here.
if dylib {
cmd.arg(&obj_filename.with_extension("metadata.o"));
cmd.add_object(&obj_filename.with_extension("metadata.o"));
}
if t.options.is_like_osx {
// The dead_strip option to the linker specifies that functions and data
// unreachable by the entry point will be removed. This is quite useful
// with Rust's compilation model of compiling libraries at a time into
// one object file. For example, this brings hello world from 1.7MB to
// 458K.
//
// Note that this is done for both executables and dynamic libraries. We
// won't get much benefit from dylibs because LLVM will have already
// stripped away as much as it could. This has not been seen to impact
// link times negatively.
//
// -dead_strip can't be part of the pre_link_args because it's also used
// for partial linking when using multiple codegen units (-r). So we
// insert it here.
cmd.arg("-Wl,-dead_strip");
}
// If we're building a dylib, we don't use --gc-sections because LLVM has
// already done the best it can do, and we also don't want to eliminate the
// metadata. If we're building an executable, however, --gc-sections drops
// the size of hello world from 1.8MB to 597K, a 67% reduction.
if !dylib && !t.options.is_like_osx {
cmd.arg("-Wl,--gc-sections");
}
// Try to strip as much out of the generated object by removing unused
// sections if possible. See more comments in linker.rs
cmd.gc_sections(dylib);
let used_link_args = sess.cstore.get_used_link_args().borrow();
if t.options.position_independent_executables {
if !dylib && t.options.position_independent_executables {
let empty_vec = Vec::new();
let empty_str = String::new();
let args = sess.opts.cg.link_args.as_ref().unwrap_or(&empty_vec);
let mut args = args.iter().chain(used_link_args.iter());
if !dylib
&& (t.options.relocation_model == "pic"
|| *sess.opts.cg.relocation_model.as_ref()
.unwrap_or(&empty_str) == "pic")
let relocation_model = sess.opts.cg.relocation_model.as_ref()
.unwrap_or(&empty_str);
if (t.options.relocation_model == "pic" || *relocation_model == "pic")
&& !args.any(|x| *x == "-static") {
cmd.arg("-pie");
cmd.position_independent_executable();
}
}
if t.options.linker_is_gnu {
// GNU-style linkers support optimization with -O. GNU ld doesn't need a
// numeric argument, but other linkers do.
if sess.opts.optimize == config::Default ||
sess.opts.optimize == config::Aggressive {
cmd.arg("-Wl,-O1");
}
}
// Pass optimization flags down to the linker.
cmd.optimize();
// We want to prevent the compiler from accidentally leaking in any system
// libraries, so we explicitly ask gcc to not link to any libraries by
// default. Note that this does not happen for windows because windows pulls
// in some large number of libraries and I couldn't quite figure out which
// subset we wanted.
if !t.options.is_like_windows {
cmd.arg("-nodefaultlibs");
}
// Mark all dynamic libraries and executables as compatible with ASLR
// FIXME #17098: ASLR breaks gdb
if t.options.is_like_windows && sess.opts.debuginfo == NoDebugInfo {
// cmd.arg("-Wl,--dynamicbase");
}
cmd.no_default_libraries();
// Take careful note of the ordering of the arguments we pass to the linker
// here. Linkers will assume that things on the left depend on things to the
@ -1019,18 +976,7 @@ fn link_args(cmd: &mut Command,
// # Telling the linker what we're doing
if dylib {
// On mac we need to tell the linker to let this library be rpathed
if sess.target.target.options.is_like_osx {
cmd.args(&["-dynamiclib", "-Wl,-dylib"]);
if sess.opts.cg.rpath {
let mut v = OsString::from("-Wl,-install_name,@rpath/");
v.push(out_filename.file_name().unwrap());
cmd.arg(&v);
}
} else {
cmd.arg("-shared");
}
cmd.build_dylib(out_filename);
}
// FIXME (#2397): At some point we want to rpath our guesses as to
@ -1059,9 +1005,10 @@ fn link_args(cmd: &mut Command,
// Finally add all the linker arguments provided on the command line along
// with any #[link_args] attributes found inside the crate
let empty = Vec::new();
cmd.args(&sess.opts.cg.link_args.as_ref().unwrap_or(&empty));
cmd.args(&used_link_args[..]);
if let Some(ref args) = sess.opts.cg.link_args {
cmd.args(args);
}
cmd.args(&used_link_args);
}
// # Native library linking
@ -1075,21 +1022,15 @@ fn link_args(cmd: &mut Command,
// Also note that the native libraries linked here are only the ones located
// in the current crate. Upstream crates with native library dependencies
// may have their native library pulled in above.
fn add_local_native_libraries(cmd: &mut Command, sess: &Session) {
fn add_local_native_libraries(cmd: &mut Linker, sess: &Session) {
sess.target_filesearch(PathKind::All).for_each_lib_search_path(|path, k| {
match k {
PathKind::Framework => { cmd.arg("-F").arg(path); }
_ => { cmd.arg("-L").arg(&fix_windows_verbatim_for_gcc(path)); }
PathKind::Framework => { cmd.framework_path(path); }
_ => { cmd.include_path(&fix_windows_verbatim_for_gcc(path)); }
}
FileDoesntMatch
});
// Some platforms take hints about whether a library is static or dynamic.
// For those that support this, we ensure we pass the option if the library
// was flagged "static" (most defaults are dynamic) to ensure that if
// libfoo.a and libfoo.so both exist that the right one is chosen.
let takes_hints = !sess.target.target.options.is_like_osx;
let libs = sess.cstore.get_used_libraries();
let libs = libs.borrow();
@ -1100,46 +1041,29 @@ fn add_local_native_libraries(cmd: &mut Command, sess: &Session) {
kind != cstore::NativeStatic
});
// Platforms that take hints generally also support the --whole-archive
// flag. We need to pass this flag when linking static native libraries to
// ensure the entire library is included.
//
// For more details see #15460, but the gist is that the linker will strip
// away any unused objects in the archive if we don't otherwise explicitly
// reference them. This can occur for libraries which are just providing
// bindings, libraries with generic functions, etc.
if takes_hints {
cmd.arg("-Wl,--whole-archive").arg("-Wl,-Bstatic");
}
// Some platforms take hints about whether a library is static or dynamic.
// For those that support this, we ensure we pass the option if the library
// was flagged "static" (most defaults are dynamic) to ensure that if
// libfoo.a and libfoo.so both exist that the right one is chosen.
cmd.hint_static();
let search_path = archive_search_paths(sess);
for l in staticlibs {
if takes_hints {
cmd.arg(&format!("-l{}", l));
} else {
// -force_load is the OSX equivalent of --whole-archive, but it
// involves passing the full path to the library to link.
let lib = archive::find_library(&l[..],
&sess.target.target.options.staticlib_prefix,
&sess.target.target.options.staticlib_suffix,
&search_path[..],
&sess.diagnostic().handler);
let mut v = OsString::from("-Wl,-force_load,");
v.push(&lib);
cmd.arg(&v);
}
}
if takes_hints {
cmd.arg("-Wl,--no-whole-archive").arg("-Wl,-Bdynamic");
// Here we explicitly ask that the entire archive is included into the
// result artifact. For more details see #15460, but the gist is that
// the linker will strip away any unused objects in the archive if we
// don't otherwise explicitly reference them. This can occur for
// libraries which are just providing bindings, libraries with generic
// functions, etc.
cmd.link_whole_staticlib(l, &search_path);
}
cmd.hint_dynamic();
for &(ref l, kind) in others {
match kind {
cstore::NativeUnknown => {
cmd.arg(&format!("-l{}", l));
}
cstore::NativeFramework => {
cmd.arg("-framework").arg(&l[..]);
}
cstore::NativeUnknown => cmd.link_dylib(l),
cstore::NativeFramework => cmd.link_framework(l),
cstore::NativeStatic => unreachable!(),
}
}
@ -1150,7 +1074,7 @@ fn add_local_native_libraries(cmd: &mut Command, sess: &Session) {
// Rust crates are not considered at all when creating an rlib output. All
// dependencies will be linked when producing the final output (instead of
// the intermediate rlib version)
fn add_upstream_rust_crates(cmd: &mut Command, sess: &Session,
fn add_upstream_rust_crates(cmd: &mut Linker, sess: &Session,
dylib: bool, tmpdir: &Path,
trans: &CrateTranslation) {
// All of the heavy lifting has previously been accomplished by the
@ -1201,7 +1125,7 @@ fn add_upstream_rust_crates(cmd: &mut Command, sess: &Session,
}
// Adds the static "rlib" versions of all crates to the command line.
fn add_static_crate(cmd: &mut Command, sess: &Session, tmpdir: &Path,
fn add_static_crate(cmd: &mut Linker, sess: &Session, tmpdir: &Path,
cratepath: &Path) {
// When performing LTO on an executable output, all of the
// bytecode from the upstream libraries has already been
@ -1263,16 +1187,16 @@ fn add_upstream_rust_crates(cmd: &mut Command, sess: &Session,
archive.remove_file(&format!("{}.o", name));
let files = archive.files();
if files.iter().any(|s| s.ends_with(".o")) {
cmd.arg(&dst);
cmd.link_rlib(&dst);
}
});
} else {
cmd.arg(&fix_windows_verbatim_for_gcc(cratepath));
cmd.link_rlib(&fix_windows_verbatim_for_gcc(cratepath));
}
}
// Same thing as above, but for dynamic crates instead of static crates.
fn add_dynamic_crate(cmd: &mut Command, sess: &Session, cratepath: &Path) {
fn add_dynamic_crate(cmd: &mut Linker, sess: &Session, cratepath: &Path) {
// If we're performing LTO, then it should have been previously required
// that all upstream rust dependencies were available in an rlib format.
assert!(!sess.lto());
@ -1280,10 +1204,10 @@ fn add_upstream_rust_crates(cmd: &mut Command, sess: &Session,
// Just need to tell the linker about where the library lives and
// what its name is
if let Some(dir) = cratepath.parent() {
cmd.arg("-L").arg(&fix_windows_verbatim_for_gcc(dir));
cmd.include_path(&fix_windows_verbatim_for_gcc(dir));
}
let filestem = cratepath.file_stem().unwrap().to_str().unwrap();
cmd.arg(&format!("-l{}", unlib(&sess.target, filestem)));
cmd.link_dylib(&unlib(&sess.target, filestem));
}
}
@ -1305,7 +1229,7 @@ fn add_upstream_rust_crates(cmd: &mut Command, sess: &Session,
// generic function calls a native function, then the generic function must
// be instantiated in the target crate, meaning that the native symbol must
// also be resolved in the target crate.
fn add_upstream_native_libraries(cmd: &mut Command, sess: &Session) {
fn add_upstream_native_libraries(cmd: &mut Linker, sess: &Session) {
// Be sure to use a topological sorting of crates because there may be
// interdependencies between native libraries. When passing -nodefaultlibs,
// for example, almost all native libraries depend on libc, so we have to
@ -1320,13 +1244,8 @@ fn add_upstream_native_libraries(cmd: &mut Command, sess: &Session) {
let libs = csearch::get_native_libraries(&sess.cstore, cnum);
for &(kind, ref lib) in &libs {
match kind {
cstore::NativeUnknown => {
cmd.arg(&format!("-l{}", *lib));
}
cstore::NativeFramework => {
cmd.arg("-framework");
cmd.arg(&lib[..]);
}
cstore::NativeUnknown => cmd.link_dylib(lib),
cstore::NativeFramework => cmd.link_framework(lib),
cstore::NativeStatic => {
sess.bug("statics shouldn't be propagated");
}

View file

@ -0,0 +1,175 @@
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use std::ffi::OsString;
use std::path::{Path, PathBuf};
use std::process::Command;
use rustc_back::archive;
use session::Session;
use session::config;
/// Linker abstraction used by back::link to build up the command to invoke a
/// linker.
///
/// This trait is the total list of requirements needed by `back::link` and
/// represents the meaning of each option being passed down. This trait is then
/// used to dispatch on whether a GNU-like linker (generally `ld.exe`) or an
/// MSVC linker (e.g. `link.exe`) is being used.
pub trait Linker {
fn link_dylib(&mut self, lib: &str);
fn link_framework(&mut self, framework: &str);
fn link_staticlib(&mut self, lib: &str);
fn link_rlib(&mut self, lib: &Path);
fn link_whole_staticlib(&mut self, lib: &str, search_path: &[PathBuf]);
fn include_path(&mut self, path: &Path);
fn framework_path(&mut self, path: &Path);
fn output_filename(&mut self, path: &Path);
fn add_object(&mut self, path: &Path);
fn gc_sections(&mut self, is_dylib: bool);
fn position_independent_executable(&mut self);
fn optimize(&mut self);
fn no_default_libraries(&mut self);
fn build_dylib(&mut self, out_filename: &Path);
fn args(&mut self, args: &[String]);
fn hint_static(&mut self);
fn hint_dynamic(&mut self);
fn whole_archives(&mut self);
fn no_whole_archives(&mut self);
}
pub struct GnuLinker<'a> {
pub cmd: &'a mut Command,
pub sess: &'a Session,
}
impl<'a> GnuLinker<'a> {
fn takes_hints(&self) -> bool {
!self.sess.target.target.options.is_like_osx
}
}
impl<'a> Linker for GnuLinker<'a> {
fn link_dylib(&mut self, lib: &str) { self.cmd.arg("-l").arg(lib); }
fn link_staticlib(&mut self, lib: &str) { self.cmd.arg("-l").arg(lib); }
fn link_rlib(&mut self, lib: &Path) { self.cmd.arg(lib); }
fn include_path(&mut self, path: &Path) { self.cmd.arg("-L").arg(path); }
fn framework_path(&mut self, path: &Path) { self.cmd.arg("-F").arg(path); }
fn output_filename(&mut self, path: &Path) { self.cmd.arg("-o").arg(path); }
fn add_object(&mut self, path: &Path) { self.cmd.arg(path); }
fn position_independent_executable(&mut self) { self.cmd.arg("-pie"); }
fn args(&mut self, args: &[String]) { self.cmd.args(args); }
fn link_framework(&mut self, framework: &str) {
self.cmd.arg("-framework").arg(framework);
}
fn link_whole_staticlib(&mut self, lib: &str, search_path: &[PathBuf]) {
let target = &self.sess.target.target;
if !target.options.is_like_osx {
self.cmd.arg("-Wl,--whole-archive")
.arg("-l").arg(lib)
.arg("-Wl,--no-whole-archive");
} else {
// -force_load is the OSX equivalent of --whole-archive, but it
// involves passing the full path to the library to link.
let mut v = OsString::from("-Wl,-force_load,");
v.push(&archive::find_library(lib,
&target.options.staticlib_prefix,
&target.options.staticlib_suffix,
search_path,
&self.sess.diagnostic().handler));
self.cmd.arg(&v);
}
}
fn gc_sections(&mut self, is_dylib: bool) {
// The dead_strip option to the linker specifies that functions and data
// unreachable by the entry point will be removed. This is quite useful
// with Rust's compilation model of compiling libraries at a time into
// one object file. For example, this brings hello world from 1.7MB to
// 458K.
//
// Note that this is done for both executables and dynamic libraries. We
// won't get much benefit from dylibs because LLVM will have already
// stripped away as much as it could. This has not been seen to impact
// link times negatively.
//
// -dead_strip can't be part of the pre_link_args because it's also used
// for partial linking when using multiple codegen units (-r). So we
// insert it here.
if self.sess.target.target.options.is_like_osx {
self.cmd.arg("-Wl,-dead_strip");
// If we're building a dylib, we don't use --gc-sections because LLVM
// has already done the best it can do, and we also don't want to
// eliminate the metadata. If we're building an executable, however,
// --gc-sections drops the size of hello world from 1.8MB to 597K, a 67%
// reduction.
} else if !is_dylib {
self.cmd.arg("-Wl,--gc-sections");
}
}
fn optimize(&mut self) {
if !self.sess.target.target.options.linker_is_gnu { return }
// GNU-style linkers support optimization with -O. GNU ld doesn't
// need a numeric argument, but other linkers do.
if self.sess.opts.optimize == config::Default ||
self.sess.opts.optimize == config::Aggressive {
self.cmd.arg("-Wl,-O1");
}
}
fn no_default_libraries(&mut self) {
// Unfortunately right now passing -nodefaultlibs to gcc on windows
// doesn't work so hot (in terms of native dependencies). This if
// statement should hopefully be removed one day though!
if !self.sess.target.target.options.is_like_windows {
self.cmd.arg("-nodefaultlibs");
}
}
fn build_dylib(&mut self, out_filename: &Path) {
// On mac we need to tell the linker to let this library be rpathed
if self.sess.target.target.options.is_like_osx {
self.cmd.args(&["-dynamiclib", "-Wl,-dylib"]);
if self.sess.opts.cg.rpath {
let mut v = OsString::from("-Wl,-install_name,@rpath/");
v.push(out_filename.file_name().unwrap());
self.cmd.arg(&v);
}
} else {
self.cmd.arg("-shared");
}
}
fn whole_archives(&mut self) {
if !self.takes_hints() { return }
self.cmd.arg("-Wl,--whole-archive");
}
fn no_whole_archives(&mut self) {
if !self.takes_hints() { return }
self.cmd.arg("-Wl,--no-whole-archive");
}
fn hint_static(&mut self) {
if !self.takes_hints() { return }
self.cmd.arg("-Wl,-Bstatic");
}
fn hint_dynamic(&mut self) {
if !self.takes_hints() { return }
self.cmd.arg("-Wl,-Bdynamic");
}
}

View file

@ -74,6 +74,7 @@ pub mod back {
pub use rustc_back::x86;
pub use rustc_back::x86_64;
pub mod linker;
pub mod link;
pub mod lto;
pub mod write;