Move some function from cg_llvm/back/link.rs to cg_ssa/back/link.rs
This commit is contained in:
parent
7754865892
commit
8a8837967f
2 changed files with 382 additions and 375 deletions
|
|
@ -7,15 +7,14 @@ use crate::metadata::METADATA_FILENAME;
|
|||
use crate::context::get_reloc_model;
|
||||
use crate::llvm;
|
||||
use rustc_codegen_ssa::back::linker::Linker;
|
||||
use rustc_codegen_ssa::back::link::{remove, ignored_for_lto, each_linked_rlib, linker_and_flavor,
|
||||
get_linker};
|
||||
use rustc_codegen_ssa::back::link::*;
|
||||
use rustc_codegen_ssa::back::command::Command;
|
||||
use rustc::session::config::{self, DebugInfo, OutputFilenames, OutputType, PrintRequest};
|
||||
use rustc::session::config::{RUST_CGU_EXT, Lto, Sanitizer};
|
||||
use rustc::session::config::{RUST_CGU_EXT, Sanitizer};
|
||||
use rustc::session::filesearch;
|
||||
use rustc::session::search_paths::PathKind;
|
||||
use rustc::session::Session;
|
||||
use rustc::middle::cstore::{NativeLibrary, NativeLibraryKind};
|
||||
use rustc::middle::cstore::NativeLibraryKind;
|
||||
use rustc::middle::dependency_format::Linkage;
|
||||
use rustc_codegen_ssa::CodegenResults;
|
||||
use rustc::util::common::{time, time_ext};
|
||||
|
|
@ -28,14 +27,10 @@ use rustc_data_structures::fx::FxHashSet;
|
|||
use std::ascii;
|
||||
use std::char;
|
||||
use std::env;
|
||||
use std::fmt;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::iter;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Output, Stdio};
|
||||
use std::str;
|
||||
use syntax::attr;
|
||||
|
||||
pub use rustc_codegen_utils::link::{find_crate_name, filename_for_input, default_output_for_target,
|
||||
invalid_output_for_target, filename_for_metadata,
|
||||
|
|
@ -96,52 +91,6 @@ pub(crate) fn link_binary(sess: &Session,
|
|||
out_filenames
|
||||
}
|
||||
|
||||
/// Returns a boolean indicating whether we should preserve the object files on
|
||||
/// the filesystem for their debug information. This is often useful with
|
||||
/// split-dwarf like schemes.
|
||||
fn preserve_objects_for_their_debuginfo(sess: &Session) -> bool {
|
||||
// If the objects don't have debuginfo there's nothing to preserve.
|
||||
if sess.opts.debuginfo == DebugInfo::None {
|
||||
return false
|
||||
}
|
||||
|
||||
// If we're only producing artifacts that are archives, no need to preserve
|
||||
// the objects as they're losslessly contained inside the archives.
|
||||
let output_linked = sess.crate_types.borrow()
|
||||
.iter()
|
||||
.any(|&x| x != config::CrateType::Rlib && x != config::CrateType::Staticlib);
|
||||
if !output_linked {
|
||||
return false
|
||||
}
|
||||
|
||||
// If we're on OSX then the equivalent of split dwarf is turned on by
|
||||
// default. The final executable won't actually have any debug information
|
||||
// except it'll have pointers to elsewhere. Historically we've always run
|
||||
// `dsymutil` to "link all the dwarf together" but this is actually sort of
|
||||
// a bummer for incremental compilation! (the whole point of split dwarf is
|
||||
// that you don't do this sort of dwarf link).
|
||||
//
|
||||
// Basically as a result this just means that if we're on OSX and we're
|
||||
// *not* running dsymutil then the object files are the only source of truth
|
||||
// for debug information, so we must preserve them.
|
||||
if sess.target.target.options.is_like_osx {
|
||||
match sess.opts.debugging_opts.run_dsymutil {
|
||||
// dsymutil is not being run, preserve objects
|
||||
Some(false) => return true,
|
||||
|
||||
// dsymutil is being run, no need to preserve the objects
|
||||
Some(true) => return false,
|
||||
|
||||
// The default historical behavior was to always run dsymutil, so
|
||||
// we're preserving that temporarily, but we're likely to switch the
|
||||
// default soon.
|
||||
None => return false,
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn link_binary_output(sess: &Session,
|
||||
codegen_results: &CodegenResults,
|
||||
crate_type: config::CrateType,
|
||||
|
|
@ -201,10 +150,6 @@ fn link_binary_output(sess: &Session,
|
|||
out_filenames
|
||||
}
|
||||
|
||||
fn archive_search_paths(sess: &Session) -> Vec<PathBuf> {
|
||||
sess.target_filesearch(PathKind::Native).search_path_dirs()
|
||||
}
|
||||
|
||||
fn archive_config<'a>(sess: &'a Session,
|
||||
output: &Path,
|
||||
input: Option<&Path>) -> ArchiveConfig<'a> {
|
||||
|
|
@ -415,53 +360,6 @@ fn link_staticlib(sess: &Session,
|
|||
}
|
||||
}
|
||||
|
||||
fn print_native_static_libs(sess: &Session, all_native_libs: &[NativeLibrary]) {
|
||||
let lib_args: Vec<_> = all_native_libs.iter()
|
||||
.filter(|l| relevant_lib(sess, l))
|
||||
.filter_map(|lib| {
|
||||
let name = lib.name?;
|
||||
match lib.kind {
|
||||
NativeLibraryKind::NativeStaticNobundle |
|
||||
NativeLibraryKind::NativeUnknown => {
|
||||
if sess.target.target.options.is_like_msvc {
|
||||
Some(format!("{}.lib", name))
|
||||
} else {
|
||||
Some(format!("-l{}", name))
|
||||
}
|
||||
},
|
||||
NativeLibraryKind::NativeFramework => {
|
||||
// ld-only syntax, since there are no frameworks in MSVC
|
||||
Some(format!("-framework {}", name))
|
||||
},
|
||||
// These are included, no need to print them
|
||||
NativeLibraryKind::NativeStatic => None,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
if !lib_args.is_empty() {
|
||||
sess.note_without_error("Link against the following native artifacts when linking \
|
||||
against this static library. The order and any duplication \
|
||||
can be significant on some platforms.");
|
||||
// Prefix for greppability
|
||||
sess.note_without_error(&format!("native-static-libs: {}", &lib_args.join(" ")));
|
||||
}
|
||||
}
|
||||
|
||||
fn get_file_path(sess: &Session, name: &str) -> PathBuf {
|
||||
let fs = sess.target_filesearch(PathKind::Native);
|
||||
let file_path = fs.get_lib_path().join(name);
|
||||
if file_path.exists() {
|
||||
return file_path
|
||||
}
|
||||
for search_path in fs.search_paths() {
|
||||
let file_path = search_path.dir.join(name);
|
||||
if file_path.exists() {
|
||||
return file_path
|
||||
}
|
||||
}
|
||||
PathBuf::from(name)
|
||||
}
|
||||
|
||||
// Create a dynamic library or executable
|
||||
//
|
||||
// This will invoke the system linker/cc to create the resulting file. This
|
||||
|
|
@ -706,147 +604,6 @@ fn link_natively(sess: &Session,
|
|||
}
|
||||
}
|
||||
|
||||
fn exec_linker(sess: &Session, cmd: &mut Command, out_filename: &Path, tmpdir: &Path)
|
||||
-> io::Result<Output>
|
||||
{
|
||||
// When attempting to spawn the linker we run a risk of blowing out the
|
||||
// size limits for spawning a new process with respect to the arguments
|
||||
// we pass on the command line.
|
||||
//
|
||||
// Here we attempt to handle errors from the OS saying "your list of
|
||||
// arguments is too big" by reinvoking the linker again with an `@`-file
|
||||
// that contains all the arguments. The theory is that this is then
|
||||
// accepted on all linkers and the linker will read all its options out of
|
||||
// there instead of looking at the command line.
|
||||
if !cmd.very_likely_to_exceed_some_spawn_limit() {
|
||||
match cmd.command().stdout(Stdio::piped()).stderr(Stdio::piped()).spawn() {
|
||||
Ok(child) => {
|
||||
let output = child.wait_with_output();
|
||||
flush_linked_file(&output, out_filename)?;
|
||||
return output;
|
||||
}
|
||||
Err(ref e) if command_line_too_big(e) => {
|
||||
info!("command line to linker was too big: {}", e);
|
||||
}
|
||||
Err(e) => return Err(e)
|
||||
}
|
||||
}
|
||||
|
||||
info!("falling back to passing arguments to linker via an @-file");
|
||||
let mut cmd2 = cmd.clone();
|
||||
let mut args = String::new();
|
||||
for arg in cmd2.take_args() {
|
||||
args.push_str(&Escape {
|
||||
arg: arg.to_str().unwrap(),
|
||||
is_like_msvc: sess.target.target.options.is_like_msvc,
|
||||
}.to_string());
|
||||
args.push_str("\n");
|
||||
}
|
||||
let file = tmpdir.join("linker-arguments");
|
||||
let bytes = if sess.target.target.options.is_like_msvc {
|
||||
let mut out = Vec::with_capacity((1 + args.len()) * 2);
|
||||
// start the stream with a UTF-16 BOM
|
||||
for c in iter::once(0xFEFF).chain(args.encode_utf16()) {
|
||||
// encode in little endian
|
||||
out.push(c as u8);
|
||||
out.push((c >> 8) as u8);
|
||||
}
|
||||
out
|
||||
} else {
|
||||
args.into_bytes()
|
||||
};
|
||||
fs::write(&file, &bytes)?;
|
||||
cmd2.arg(format!("@{}", file.display()));
|
||||
info!("invoking linker {:?}", cmd2);
|
||||
let output = cmd2.output();
|
||||
flush_linked_file(&output, out_filename)?;
|
||||
return output;
|
||||
|
||||
#[cfg(unix)]
|
||||
fn flush_linked_file(_: &io::Result<Output>, _: &Path) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn flush_linked_file(command_output: &io::Result<Output>, out_filename: &Path)
|
||||
-> io::Result<()>
|
||||
{
|
||||
// On Windows, under high I/O load, output buffers are sometimes not flushed,
|
||||
// even long after process exit, causing nasty, non-reproducible output bugs.
|
||||
//
|
||||
// File::sync_all() calls FlushFileBuffers() down the line, which solves the problem.
|
||||
//
|
||||
// А full writeup of the original Chrome bug can be found at
|
||||
// randomascii.wordpress.com/2018/02/25/compiler-bug-linker-bug-windows-kernel-bug/amp
|
||||
|
||||
if let &Ok(ref out) = command_output {
|
||||
if out.status.success() {
|
||||
if let Ok(of) = fs::OpenOptions::new().write(true).open(out_filename) {
|
||||
of.sync_all()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn command_line_too_big(err: &io::Error) -> bool {
|
||||
err.raw_os_error() == Some(::libc::E2BIG)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn command_line_too_big(err: &io::Error) -> bool {
|
||||
const ERROR_FILENAME_EXCED_RANGE: i32 = 206;
|
||||
err.raw_os_error() == Some(ERROR_FILENAME_EXCED_RANGE)
|
||||
}
|
||||
|
||||
struct Escape<'a> {
|
||||
arg: &'a str,
|
||||
is_like_msvc: bool,
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for Escape<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.is_like_msvc {
|
||||
// This is "documented" at
|
||||
// https://msdn.microsoft.com/en-us/library/4xdcbak7.aspx
|
||||
//
|
||||
// Unfortunately there's not a great specification of the
|
||||
// syntax I could find online (at least) but some local
|
||||
// testing showed that this seemed sufficient-ish to catch
|
||||
// at least a few edge cases.
|
||||
write!(f, "\"")?;
|
||||
for c in self.arg.chars() {
|
||||
match c {
|
||||
'"' => write!(f, "\\{}", c)?,
|
||||
c => write!(f, "{}", c)?,
|
||||
}
|
||||
}
|
||||
write!(f, "\"")?;
|
||||
} else {
|
||||
// This is documented at https://linux.die.net/man/1/ld, namely:
|
||||
//
|
||||
// > Options in file are separated by whitespace. A whitespace
|
||||
// > character may be included in an option by surrounding the
|
||||
// > entire option in either single or double quotes. Any
|
||||
// > character (including a backslash) may be included by
|
||||
// > prefixing the character to be included with a backslash.
|
||||
//
|
||||
// We put an argument on each line, so all we need to do is
|
||||
// ensure the line is interpreted as one whole argument.
|
||||
for c in self.arg.chars() {
|
||||
match c {
|
||||
'\\' | ' ' => write!(f, "\\{}", c)?,
|
||||
c => write!(f, "{}", c)?,
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn link_args(cmd: &mut dyn Linker,
|
||||
flavor: LinkerFlavor,
|
||||
sess: &Session,
|
||||
|
|
@ -1051,48 +808,6 @@ fn link_args(cmd: &mut dyn Linker,
|
|||
cmd.args(&used_link_args);
|
||||
}
|
||||
|
||||
// # Native library linking
|
||||
//
|
||||
// User-supplied library search paths (-L on the command line). These are
|
||||
// the same paths used to find Rust crates, so some of them may have been
|
||||
// added already by the previous crate linking code. This only allows them
|
||||
// to be found at compile time so it is still entirely up to outside
|
||||
// forces to make sure that library can be found at runtime.
|
||||
//
|
||||
// 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 dyn Linker,
|
||||
sess: &Session,
|
||||
codegen_results: &CodegenResults) {
|
||||
let filesearch = sess.target_filesearch(PathKind::All);
|
||||
for search_path in filesearch.search_paths() {
|
||||
match search_path.kind {
|
||||
PathKind::Framework => { cmd.framework_path(&search_path.dir); }
|
||||
_ => { cmd.include_path(&fix_windows_verbatim_for_gcc(&search_path.dir)); }
|
||||
}
|
||||
}
|
||||
|
||||
let relevant_libs = codegen_results.crate_info.used_libraries.iter().filter(|l| {
|
||||
relevant_lib(sess, l)
|
||||
});
|
||||
|
||||
let search_path = archive_search_paths(sess);
|
||||
for lib in relevant_libs {
|
||||
let name = match lib.name {
|
||||
Some(ref l) => l,
|
||||
None => continue,
|
||||
};
|
||||
match lib.kind {
|
||||
NativeLibraryKind::NativeUnknown => cmd.link_dylib(&name.as_str()),
|
||||
NativeLibraryKind::NativeFramework => cmd.link_framework(&name.as_str()),
|
||||
NativeLibraryKind::NativeStaticNobundle => cmd.link_staticlib(&name.as_str()),
|
||||
NativeLibraryKind::NativeStatic => cmd.link_whole_staticlib(&name.as_str(),
|
||||
&search_path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// # Rust Crate linking
|
||||
//
|
||||
// Rust crates are not considered at all when creating an rlib output. All
|
||||
|
|
@ -1407,88 +1122,3 @@ fn add_upstream_rust_crates(cmd: &mut dyn Linker,
|
|||
parent.unwrap_or(Path::new("")));
|
||||
}
|
||||
}
|
||||
|
||||
// Link in all of our upstream crates' native dependencies. Remember that
|
||||
// all of these upstream native dependencies are all non-static
|
||||
// dependencies. We've got two cases then:
|
||||
//
|
||||
// 1. The upstream crate is an rlib. In this case we *must* link in the
|
||||
// native dependency because the rlib is just an archive.
|
||||
//
|
||||
// 2. The upstream crate is a dylib. In order to use the dylib, we have to
|
||||
// have the dependency present on the system somewhere. Thus, we don't
|
||||
// gain a whole lot from not linking in the dynamic dependency to this
|
||||
// crate as well.
|
||||
//
|
||||
// The use case for this is a little subtle. In theory the native
|
||||
// dependencies of a crate are purely an implementation detail of the crate
|
||||
// itself, but the problem arises with generic and inlined functions. If a
|
||||
// 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 dyn Linker,
|
||||
sess: &Session,
|
||||
codegen_results: &CodegenResults,
|
||||
crate_type: config::CrateType) {
|
||||
// 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
|
||||
// make sure that's all the way at the right (liblibc is near the base of
|
||||
// the dependency chain).
|
||||
//
|
||||
// This passes RequireStatic, but the actual requirement doesn't matter,
|
||||
// we're just getting an ordering of crate numbers, we're not worried about
|
||||
// the paths.
|
||||
let formats = sess.dependency_formats.borrow();
|
||||
let data = formats.get(&crate_type).unwrap();
|
||||
|
||||
let crates = &codegen_results.crate_info.used_crates_static;
|
||||
for &(cnum, _) in crates {
|
||||
for lib in codegen_results.crate_info.native_libraries[&cnum].iter() {
|
||||
let name = match lib.name {
|
||||
Some(ref l) => l,
|
||||
None => continue,
|
||||
};
|
||||
if !relevant_lib(sess, &lib) {
|
||||
continue
|
||||
}
|
||||
match lib.kind {
|
||||
NativeLibraryKind::NativeUnknown => cmd.link_dylib(&name.as_str()),
|
||||
NativeLibraryKind::NativeFramework => cmd.link_framework(&name.as_str()),
|
||||
NativeLibraryKind::NativeStaticNobundle => {
|
||||
// Link "static-nobundle" native libs only if the crate they originate from
|
||||
// is being linked statically to the current crate. If it's linked dynamically
|
||||
// or is an rlib already included via some other dylib crate, the symbols from
|
||||
// native libs will have already been included in that dylib.
|
||||
if data[cnum.as_usize() - 1] == Linkage::Static {
|
||||
cmd.link_staticlib(&name.as_str())
|
||||
}
|
||||
},
|
||||
// ignore statically included native libraries here as we've
|
||||
// already included them when we included the rust library
|
||||
// previously
|
||||
NativeLibraryKind::NativeStatic => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn relevant_lib(sess: &Session, lib: &NativeLibrary) -> bool {
|
||||
match lib.cfg {
|
||||
Some(ref cfg) => attr::cfg_matches(cfg, &sess.parse_sess, None),
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn are_upstream_rust_objects_already_included(sess: &Session) -> bool {
|
||||
match sess.lto() {
|
||||
Lto::Fat => true,
|
||||
Lto::Thin => {
|
||||
// If we defer LTO to the linker, we haven't run LTO ourselves, so
|
||||
// any upstream object files have not been copied yet.
|
||||
!sess.opts.cg.linker_plugin_lto.enabled()
|
||||
}
|
||||
Lto::No |
|
||||
Lto::ThinLocal => false,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,18 +4,25 @@
|
|||
use rustc::session::{Session, config};
|
||||
use rustc::session::search_paths::PathKind;
|
||||
use rustc::middle::dependency_format::Linkage;
|
||||
use rustc::middle::cstore::LibSource;
|
||||
use rustc::middle::cstore::{LibSource, NativeLibrary, NativeLibraryKind};
|
||||
use rustc_target::spec::LinkerFlavor;
|
||||
use rustc::hir::def_id::CrateNum;
|
||||
use rustc_fs_util::fix_windows_verbatim_for_gcc;
|
||||
|
||||
use super::command::Command;
|
||||
use crate::CrateInfo;
|
||||
use crate::{CrateInfo, CodegenResults};
|
||||
use crate::back::linker::Linker;
|
||||
|
||||
use cc::windows_registry;
|
||||
use std::fmt;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Output, Stdio};
|
||||
use std::env;
|
||||
|
||||
pub use rustc_codegen_utils::link::*;
|
||||
|
||||
pub fn remove(sess: &Session, path: &Path) {
|
||||
if let Err(e) = fs::remove_file(path) {
|
||||
sess.err(&format!("failed to remove {}: {}",
|
||||
|
|
@ -200,3 +207,373 @@ pub fn linker_and_flavor(sess: &Session) -> (PathBuf, LinkerFlavor) {
|
|||
|
||||
bug!("Not enough information provided to determine how to invoke the linker");
|
||||
}
|
||||
|
||||
/// Returns a boolean indicating whether we should preserve the object files on
|
||||
/// the filesystem for their debug information. This is often useful with
|
||||
/// split-dwarf like schemes.
|
||||
pub fn preserve_objects_for_their_debuginfo(sess: &Session) -> bool {
|
||||
// If the objects don't have debuginfo there's nothing to preserve.
|
||||
if sess.opts.debuginfo == config::DebugInfo::None {
|
||||
return false
|
||||
}
|
||||
|
||||
// If we're only producing artifacts that are archives, no need to preserve
|
||||
// the objects as they're losslessly contained inside the archives.
|
||||
let output_linked = sess.crate_types.borrow()
|
||||
.iter()
|
||||
.any(|&x| x != config::CrateType::Rlib && x != config::CrateType::Staticlib);
|
||||
if !output_linked {
|
||||
return false
|
||||
}
|
||||
|
||||
// If we're on OSX then the equivalent of split dwarf is turned on by
|
||||
// default. The final executable won't actually have any debug information
|
||||
// except it'll have pointers to elsewhere. Historically we've always run
|
||||
// `dsymutil` to "link all the dwarf together" but this is actually sort of
|
||||
// a bummer for incremental compilation! (the whole point of split dwarf is
|
||||
// that you don't do this sort of dwarf link).
|
||||
//
|
||||
// Basically as a result this just means that if we're on OSX and we're
|
||||
// *not* running dsymutil then the object files are the only source of truth
|
||||
// for debug information, so we must preserve them.
|
||||
if sess.target.target.options.is_like_osx {
|
||||
match sess.opts.debugging_opts.run_dsymutil {
|
||||
// dsymutil is not being run, preserve objects
|
||||
Some(false) => return true,
|
||||
|
||||
// dsymutil is being run, no need to preserve the objects
|
||||
Some(true) => return false,
|
||||
|
||||
// The default historical behavior was to always run dsymutil, so
|
||||
// we're preserving that temporarily, but we're likely to switch the
|
||||
// default soon.
|
||||
None => return false,
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn archive_search_paths(sess: &Session) -> Vec<PathBuf> {
|
||||
sess.target_filesearch(PathKind::Native).search_path_dirs()
|
||||
}
|
||||
|
||||
enum RlibFlavor {
|
||||
Normal,
|
||||
StaticlibBase,
|
||||
}
|
||||
|
||||
pub fn print_native_static_libs(sess: &Session, all_native_libs: &[NativeLibrary]) {
|
||||
let lib_args: Vec<_> = all_native_libs.iter()
|
||||
.filter(|l| relevant_lib(sess, l))
|
||||
.filter_map(|lib| {
|
||||
let name = lib.name?;
|
||||
match lib.kind {
|
||||
NativeLibraryKind::NativeStaticNobundle |
|
||||
NativeLibraryKind::NativeUnknown => {
|
||||
if sess.target.target.options.is_like_msvc {
|
||||
Some(format!("{}.lib", name))
|
||||
} else {
|
||||
Some(format!("-l{}", name))
|
||||
}
|
||||
},
|
||||
NativeLibraryKind::NativeFramework => {
|
||||
// ld-only syntax, since there are no frameworks in MSVC
|
||||
Some(format!("-framework {}", name))
|
||||
},
|
||||
// These are included, no need to print them
|
||||
NativeLibraryKind::NativeStatic => None,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
if !lib_args.is_empty() {
|
||||
sess.note_without_error("Link against the following native artifacts when linking \
|
||||
against this static library. The order and any duplication \
|
||||
can be significant on some platforms.");
|
||||
// Prefix for greppability
|
||||
sess.note_without_error(&format!("native-static-libs: {}", &lib_args.join(" ")));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_file_path(sess: &Session, name: &str) -> PathBuf {
|
||||
let fs = sess.target_filesearch(PathKind::Native);
|
||||
let file_path = fs.get_lib_path().join(name);
|
||||
if file_path.exists() {
|
||||
return file_path
|
||||
}
|
||||
for search_path in fs.search_paths() {
|
||||
let file_path = search_path.dir.join(name);
|
||||
if file_path.exists() {
|
||||
return file_path
|
||||
}
|
||||
}
|
||||
PathBuf::from(name)
|
||||
}
|
||||
|
||||
pub fn exec_linker(sess: &Session, cmd: &mut Command, out_filename: &Path, tmpdir: &Path)
|
||||
-> io::Result<Output>
|
||||
{
|
||||
// When attempting to spawn the linker we run a risk of blowing out the
|
||||
// size limits for spawning a new process with respect to the arguments
|
||||
// we pass on the command line.
|
||||
//
|
||||
// Here we attempt to handle errors from the OS saying "your list of
|
||||
// arguments is too big" by reinvoking the linker again with an `@`-file
|
||||
// that contains all the arguments. The theory is that this is then
|
||||
// accepted on all linkers and the linker will read all its options out of
|
||||
// there instead of looking at the command line.
|
||||
if !cmd.very_likely_to_exceed_some_spawn_limit() {
|
||||
match cmd.command().stdout(Stdio::piped()).stderr(Stdio::piped()).spawn() {
|
||||
Ok(child) => {
|
||||
let output = child.wait_with_output();
|
||||
flush_linked_file(&output, out_filename)?;
|
||||
return output;
|
||||
}
|
||||
Err(ref e) if command_line_too_big(e) => {
|
||||
info!("command line to linker was too big: {}", e);
|
||||
}
|
||||
Err(e) => return Err(e)
|
||||
}
|
||||
}
|
||||
|
||||
info!("falling back to passing arguments to linker via an @-file");
|
||||
let mut cmd2 = cmd.clone();
|
||||
let mut args = String::new();
|
||||
for arg in cmd2.take_args() {
|
||||
args.push_str(&Escape {
|
||||
arg: arg.to_str().unwrap(),
|
||||
is_like_msvc: sess.target.target.options.is_like_msvc,
|
||||
}.to_string());
|
||||
args.push_str("\n");
|
||||
}
|
||||
let file = tmpdir.join("linker-arguments");
|
||||
let bytes = if sess.target.target.options.is_like_msvc {
|
||||
let mut out = Vec::with_capacity((1 + args.len()) * 2);
|
||||
// start the stream with a UTF-16 BOM
|
||||
for c in std::iter::once(0xFEFF).chain(args.encode_utf16()) {
|
||||
// encode in little endian
|
||||
out.push(c as u8);
|
||||
out.push((c >> 8) as u8);
|
||||
}
|
||||
out
|
||||
} else {
|
||||
args.into_bytes()
|
||||
};
|
||||
fs::write(&file, &bytes)?;
|
||||
cmd2.arg(format!("@{}", file.display()));
|
||||
info!("invoking linker {:?}", cmd2);
|
||||
let output = cmd2.output();
|
||||
flush_linked_file(&output, out_filename)?;
|
||||
return output;
|
||||
|
||||
#[cfg(unix)]
|
||||
fn flush_linked_file(_: &io::Result<Output>, _: &Path) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn flush_linked_file(command_output: &io::Result<Output>, out_filename: &Path)
|
||||
-> io::Result<()>
|
||||
{
|
||||
// On Windows, under high I/O load, output buffers are sometimes not flushed,
|
||||
// even long after process exit, causing nasty, non-reproducible output bugs.
|
||||
//
|
||||
// File::sync_all() calls FlushFileBuffers() down the line, which solves the problem.
|
||||
//
|
||||
// А full writeup of the original Chrome bug can be found at
|
||||
// randomascii.wordpress.com/2018/02/25/compiler-bug-linker-bug-windows-kernel-bug/amp
|
||||
|
||||
if let &Ok(ref out) = command_output {
|
||||
if out.status.success() {
|
||||
if let Ok(of) = fs::OpenOptions::new().write(true).open(out_filename) {
|
||||
of.sync_all()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn command_line_too_big(err: &io::Error) -> bool {
|
||||
err.raw_os_error() == Some(::libc::E2BIG)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn command_line_too_big(err: &io::Error) -> bool {
|
||||
const ERROR_FILENAME_EXCED_RANGE: i32 = 206;
|
||||
err.raw_os_error() == Some(ERROR_FILENAME_EXCED_RANGE)
|
||||
}
|
||||
|
||||
struct Escape<'a> {
|
||||
arg: &'a str,
|
||||
is_like_msvc: bool,
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for Escape<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.is_like_msvc {
|
||||
// This is "documented" at
|
||||
// https://msdn.microsoft.com/en-us/library/4xdcbak7.aspx
|
||||
//
|
||||
// Unfortunately there's not a great specification of the
|
||||
// syntax I could find online (at least) but some local
|
||||
// testing showed that this seemed sufficient-ish to catch
|
||||
// at least a few edge cases.
|
||||
write!(f, "\"")?;
|
||||
for c in self.arg.chars() {
|
||||
match c {
|
||||
'"' => write!(f, "\\{}", c)?,
|
||||
c => write!(f, "{}", c)?,
|
||||
}
|
||||
}
|
||||
write!(f, "\"")?;
|
||||
} else {
|
||||
// This is documented at https://linux.die.net/man/1/ld, namely:
|
||||
//
|
||||
// > Options in file are separated by whitespace. A whitespace
|
||||
// > character may be included in an option by surrounding the
|
||||
// > entire option in either single or double quotes. Any
|
||||
// > character (including a backslash) may be included by
|
||||
// > prefixing the character to be included with a backslash.
|
||||
//
|
||||
// We put an argument on each line, so all we need to do is
|
||||
// ensure the line is interpreted as one whole argument.
|
||||
for c in self.arg.chars() {
|
||||
match c {
|
||||
'\\' | ' ' => write!(f, "\\{}", c)?,
|
||||
c => write!(f, "{}", c)?,
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// # Native library linking
|
||||
//
|
||||
// User-supplied library search paths (-L on the command line). These are
|
||||
// the same paths used to find Rust crates, so some of them may have been
|
||||
// added already by the previous crate linking code. This only allows them
|
||||
// to be found at compile time so it is still entirely up to outside
|
||||
// forces to make sure that library can be found at runtime.
|
||||
//
|
||||
// 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.
|
||||
pub fn add_local_native_libraries(cmd: &mut dyn Linker,
|
||||
sess: &Session,
|
||||
codegen_results: &CodegenResults) {
|
||||
let filesearch = sess.target_filesearch(PathKind::All);
|
||||
for search_path in filesearch.search_paths() {
|
||||
match search_path.kind {
|
||||
PathKind::Framework => { cmd.framework_path(&search_path.dir); }
|
||||
_ => { cmd.include_path(&fix_windows_verbatim_for_gcc(&search_path.dir)); }
|
||||
}
|
||||
}
|
||||
|
||||
let relevant_libs = codegen_results.crate_info.used_libraries.iter().filter(|l| {
|
||||
relevant_lib(sess, l)
|
||||
});
|
||||
|
||||
let search_path = archive_search_paths(sess);
|
||||
for lib in relevant_libs {
|
||||
let name = match lib.name {
|
||||
Some(ref l) => l,
|
||||
None => continue,
|
||||
};
|
||||
match lib.kind {
|
||||
NativeLibraryKind::NativeUnknown => cmd.link_dylib(&name.as_str()),
|
||||
NativeLibraryKind::NativeFramework => cmd.link_framework(&name.as_str()),
|
||||
NativeLibraryKind::NativeStaticNobundle => cmd.link_staticlib(&name.as_str()),
|
||||
NativeLibraryKind::NativeStatic => cmd.link_whole_staticlib(&name.as_str(),
|
||||
&search_path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Link in all of our upstream crates' native dependencies. Remember that
|
||||
// all of these upstream native dependencies are all non-static
|
||||
// dependencies. We've got two cases then:
|
||||
//
|
||||
// 1. The upstream crate is an rlib. In this case we *must* link in the
|
||||
// native dependency because the rlib is just an archive.
|
||||
//
|
||||
// 2. The upstream crate is a dylib. In order to use the dylib, we have to
|
||||
// have the dependency present on the system somewhere. Thus, we don't
|
||||
// gain a whole lot from not linking in the dynamic dependency to this
|
||||
// crate as well.
|
||||
//
|
||||
// The use case for this is a little subtle. In theory the native
|
||||
// dependencies of a crate are purely an implementation detail of the crate
|
||||
// itself, but the problem arises with generic and inlined functions. If a
|
||||
// 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.
|
||||
pub fn add_upstream_native_libraries(cmd: &mut dyn Linker,
|
||||
sess: &Session,
|
||||
codegen_results: &CodegenResults,
|
||||
crate_type: config::CrateType) {
|
||||
// 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
|
||||
// make sure that's all the way at the right (liblibc is near the base of
|
||||
// the dependency chain).
|
||||
//
|
||||
// This passes RequireStatic, but the actual requirement doesn't matter,
|
||||
// we're just getting an ordering of crate numbers, we're not worried about
|
||||
// the paths.
|
||||
let formats = sess.dependency_formats.borrow();
|
||||
let data = formats.get(&crate_type).unwrap();
|
||||
|
||||
let crates = &codegen_results.crate_info.used_crates_static;
|
||||
for &(cnum, _) in crates {
|
||||
for lib in codegen_results.crate_info.native_libraries[&cnum].iter() {
|
||||
let name = match lib.name {
|
||||
Some(ref l) => l,
|
||||
None => continue,
|
||||
};
|
||||
if !relevant_lib(sess, &lib) {
|
||||
continue
|
||||
}
|
||||
match lib.kind {
|
||||
NativeLibraryKind::NativeUnknown => cmd.link_dylib(&name.as_str()),
|
||||
NativeLibraryKind::NativeFramework => cmd.link_framework(&name.as_str()),
|
||||
NativeLibraryKind::NativeStaticNobundle => {
|
||||
// Link "static-nobundle" native libs only if the crate they originate from
|
||||
// is being linked statically to the current crate. If it's linked dynamically
|
||||
// or is an rlib already included via some other dylib crate, the symbols from
|
||||
// native libs will have already been included in that dylib.
|
||||
if data[cnum.as_usize() - 1] == Linkage::Static {
|
||||
cmd.link_staticlib(&name.as_str())
|
||||
}
|
||||
},
|
||||
// ignore statically included native libraries here as we've
|
||||
// already included them when we included the rust library
|
||||
// previously
|
||||
NativeLibraryKind::NativeStatic => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn relevant_lib(sess: &Session, lib: &NativeLibrary) -> bool {
|
||||
match lib.cfg {
|
||||
Some(ref cfg) => syntax::attr::cfg_matches(cfg, &sess.parse_sess, None),
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn are_upstream_rust_objects_already_included(sess: &Session) -> bool {
|
||||
match sess.lto() {
|
||||
config::Lto::Fat => true,
|
||||
config::Lto::Thin => {
|
||||
// If we defer LTO to the linker, we haven't run LTO ourselves, so
|
||||
// any upstream object files have not been copied yet.
|
||||
!sess.opts.cg.linker_plugin_lto.enabled()
|
||||
}
|
||||
config::Lto::No |
|
||||
config::Lto::ThinLocal => false,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue