]> git.lizzy.rs Git - rust.git/commitdiff
rustc_trans: Abstract linker support behind a trait
authorAlex Crichton <alex@alexcrichton.com>
Fri, 8 May 2015 22:31:23 +0000 (15:31 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Tue, 12 May 2015 21:50:36 +0000 (14:50 -0700)
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`.

src/librustc_trans/back/link.rs
src/librustc_trans/back/linker.rs [new file with mode: 0644]
src/librustc_trans/lib.rs

index 38ad909dd012e41eca1c583080256cbbb6cabe2a..e748f8b44cbed8ea795f1f99a72cf2112f45d2cc 100644 (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::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 unlib<'a>(config: &config::Config, stem: &'a str) -> &'a str {
     }
 
     // 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_static_crate(cmd: &mut Command, sess: &Session, tmpdir: &Path,
                 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_dynamic_crate(cmd: &mut Command, sess: &Session, cratepath: &Path) {
         // 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_dynamic_crate(cmd: &mut Command, sess: &Session, cratepath: &Path) {
 // 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 (file)
index 0000000..da1ec29
--- /dev/null
@@ -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");
+    }
+}
index 3e2db80a9c556dbe55076575c8e75dd7c916540e..2a823c69276f67f32c23cf252f74d1c41b351111 100644 (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;