diff --git a/src/librustc_trans/back/link.rs b/src/librustc_trans/back/link.rs index 38ad909dd012..e748f8b44cbe 100644 --- a/src/librustc_trans/back/link.rs +++ b/src/librustc_trans/back/link.rs @@ -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"); } diff --git a/src/librustc_trans/back/linker.rs b/src/librustc_trans/back/linker.rs new file mode 100644 index 000000000000..da1ec29a41e7 --- /dev/null +++ b/src/librustc_trans/back/linker.rs @@ -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 or the MIT license +// , 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"); + } +} diff --git a/src/librustc_trans/lib.rs b/src/librustc_trans/lib.rs index 3e2db80a9c55..2a823c69276f 100644 --- a/src/librustc_trans/lib.rs +++ b/src/librustc_trans/lib.rs @@ -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;