Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rustc_target: Refactor internal linker flavors #101988

Merged
merged 1 commit into from
Oct 7, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 45 additions & 30 deletions compiler/rustc_codegen_ssa/src/back/link.rs
Original file line number Diff line number Diff line change
@@ -23,8 +23,8 @@ use rustc_session::{filesearch, Session};
use rustc_span::symbol::Symbol;
use rustc_span::DebuggerVisualizerFile;
use rustc_target::spec::crt_objects::{CrtObjects, LinkSelfContainedDefault};
use rustc_target::spec::{LinkOutputKind, LinkerFlavor, LldFlavor, SplitDebuginfo};
use rustc_target::spec::{PanicStrategy, RelocModel, RelroLevel, SanitizerSet, Target};
use rustc_target::spec::{Cc, LinkOutputKind, LinkerFlavor, LinkerFlavorCli, Lld, PanicStrategy};
use rustc_target::spec::{RelocModel, RelroLevel, SanitizerSet, SplitDebuginfo, Target};

use super::archive::{ArchiveBuilder, ArchiveBuilderBuilder};
use super::command::Command;
@@ -748,8 +748,7 @@ fn link_natively<'a>(
// then it should not default to linking executables as pie. Different
// versions of gcc seem to use different quotes in the error message so
// don't check for them.
if sess.target.linker_is_gnu
&& flavor != LinkerFlavor::Ld
if matches!(flavor, LinkerFlavor::Gnu(Cc::Yes, _))
&& unknown_arg_regex.is_match(&out)
&& out.contains("-no-pie")
&& cmd.get_args().iter().any(|e| e.to_string_lossy() == "-no-pie")
@@ -767,8 +766,7 @@ fn link_natively<'a>(

// Detect '-static-pie' used with an older version of gcc or clang not supporting it.
// Fallback from '-static-pie' to '-static' in that case.
if sess.target.linker_is_gnu
&& flavor != LinkerFlavor::Ld
if matches!(flavor, LinkerFlavor::Gnu(Cc::Yes, _))
&& unknown_arg_regex.is_match(&out)
&& (out.contains("-static-pie") || out.contains("--no-dynamic-linker"))
&& cmd.get_args().iter().any(|e| e.to_string_lossy() == "-static-pie")
@@ -903,7 +901,7 @@ fn link_natively<'a>(
// install the Visual Studio build tools.
if let Some(code) = prog.status.code() {
if sess.target.is_like_msvc
&& flavor == LinkerFlavor::Msvc
&& flavor == LinkerFlavor::Msvc(Lld::No)
// Respect the command line override
&& sess.opts.cg.linker.is_none()
// Match exactly "link.exe"
@@ -1187,7 +1185,10 @@ pub fn linker_and_flavor(sess: &Session) -> (PathBuf, LinkerFlavor) {
// only the linker flavor is known; use the default linker for the selected flavor
(None, Some(flavor)) => Some((
PathBuf::from(match flavor {
LinkerFlavor::Gcc => {
LinkerFlavor::Gnu(Cc::Yes, _)
| LinkerFlavor::Darwin(Cc::Yes, _)
| LinkerFlavor::WasmLld(Cc::Yes)
| LinkerFlavor::Unix(Cc::Yes) => {
if cfg!(any(target_os = "solaris", target_os = "illumos")) {
// On historical Solaris systems, "cc" may have
// been Sun Studio, which is not flag-compatible
@@ -1200,9 +1201,14 @@ pub fn linker_and_flavor(sess: &Session) -> (PathBuf, LinkerFlavor) {
"cc"
}
}
LinkerFlavor::Ld => "ld",
LinkerFlavor::Lld(_) => "lld",
LinkerFlavor::Msvc => "link.exe",
LinkerFlavor::Gnu(_, Lld::Yes)
| LinkerFlavor::Darwin(_, Lld::Yes)
| LinkerFlavor::WasmLld(..)
| LinkerFlavor::Msvc(Lld::Yes) => "lld",
LinkerFlavor::Gnu(..) | LinkerFlavor::Darwin(..) | LinkerFlavor::Unix(..) => {
"ld"
}
LinkerFlavor::Msvc(..) => "link.exe",
LinkerFlavor::EmCc => {
if cfg!(windows) {
"emcc.bat"
@@ -1227,15 +1233,20 @@ pub fn linker_and_flavor(sess: &Session) -> (PathBuf, LinkerFlavor) {
|| stem == "clang"
|| stem.ends_with("-clang")
{
LinkerFlavor::Gcc
LinkerFlavor::from_cli(LinkerFlavorCli::Gcc, &sess.target)
} else if stem == "wasm-ld" || stem.ends_with("-wasm-ld") {
LinkerFlavor::Lld(LldFlavor::Wasm)
} else if stem == "ld" || stem == "ld.lld" || stem.ends_with("-ld") {
LinkerFlavor::Ld
} else if stem == "link" || stem == "lld-link" {
LinkerFlavor::Msvc
LinkerFlavor::WasmLld(Cc::No)
} else if stem == "ld" || stem.ends_with("-ld") {
LinkerFlavor::from_cli(LinkerFlavorCli::Ld, &sess.target)
} else if stem == "ld.lld" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Come to think of it, does this case even work as expected? file_stem on ld.lld would return ld meaning we never take this branch.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's reachable if it full file name was ld.lld.exe :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True but you'd think ld.lld is a lot more common than ld.lld.exe 😛

Copy link
Contributor Author

@petrochenkov petrochenkov Sep 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not impossible though!

we@we-pc MINGW64 ~
$ where ld.lld
C:\msys64\mingw64\bin\ld.lld.exe

LinkerFlavor::Gnu(Cc::No, Lld::Yes)
} else if stem == "link" {
LinkerFlavor::Msvc(Lld::No)
} else if stem == "lld-link" {
LinkerFlavor::Msvc(Lld::Yes)
} else if stem == "lld" || stem == "rust-lld" {
LinkerFlavor::Lld(sess.target.lld_flavor)
let lld_flavor = sess.target.linker_flavor.lld_flavor();
LinkerFlavor::from_cli(LinkerFlavorCli::Lld(lld_flavor), &sess.target)
} else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given we already have cases for ld.lld, lld-link and wasm-ld is there any reason not to have one for ld64.lld as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just didn't change any logic here, the linker name heuristic can be improved in multiple ways, e.g. accounting for cc and c++/g++/clang++ as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, that was just a small thing I noticed while reading through that bit. More for completeness sake since I don't think i've seen any complaints about that case missing

// fall back to the value in the target spec
sess.target.linker_flavor
@@ -1249,7 +1260,8 @@ pub fn linker_and_flavor(sess: &Session) -> (PathBuf, LinkerFlavor) {

// linker and linker flavor specified via command line have precedence over what the target
// specification specifies
let linker_flavor = sess.opts.cg.linker_flavor.map(LinkerFlavor::from_cli);
let linker_flavor =
sess.opts.cg.linker_flavor.map(|flavor| LinkerFlavor::from_cli(flavor, &sess.target));
if let Some(ret) = infer_from(sess, sess.opts.cg.linker.clone(), linker_flavor) {
return ret;
}
@@ -1320,7 +1332,7 @@ fn print_native_static_libs(sess: &Session, all_native_libs: &[NativeLib]) {
let verbatim = lib.verbatim.unwrap_or(false);
if sess.target.is_like_msvc {
Some(format!("{}{}", name, if verbatim { "" } else { ".lib" }))
} else if sess.target.linker_is_gnu {
} else if sess.target.linker_flavor.is_gnu() {
Some(format!("-l{}{}", if verbatim { ":" } else { "" }, name))
} else {
Some(format!("-l{}", name))
@@ -1607,7 +1619,7 @@ fn add_pre_link_objects(
let empty = Default::default();
let objects = if self_contained {
&opts.pre_link_objects_self_contained
} else if !(sess.target.os == "fuchsia" && flavor == LinkerFlavor::Gcc) {
} else if !(sess.target.os == "fuchsia" && matches!(flavor, LinkerFlavor::Gnu(Cc::Yes, _))) {
&opts.pre_link_objects
} else {
&empty
@@ -1647,7 +1659,7 @@ fn add_pre_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor)
fn add_link_script(cmd: &mut dyn Linker, sess: &Session, tmpdir: &Path, crate_type: CrateType) {
match (crate_type, &sess.target.link_script) {
(CrateType::Cdylib | CrateType::Executable, Some(script)) => {
if !sess.target.linker_is_gnu {
if !sess.target.linker_flavor.is_gnu() {
sess.fatal("can only use link script when linking with GNU-like linker");
}

@@ -1890,7 +1902,7 @@ fn add_rpath_args(
out_filename: out_filename.to_path_buf(),
has_rpath: sess.target.has_rpath,
is_like_osx: sess.target.is_like_osx,
linker_is_gnu: sess.target.linker_is_gnu,
linker_is_gnu: sess.target.linker_flavor.is_gnu(),
};
cmd.args(&rpath::get_rpath_flags(&mut rpath_config));
}
@@ -2104,7 +2116,7 @@ fn add_order_independent_options(

if sess.target.os == "fuchsia"
&& crate_type == CrateType::Executable
&& flavor != LinkerFlavor::Gcc
&& !matches!(flavor, LinkerFlavor::Gnu(Cc::Yes, _))
{
let prefix = if sess.opts.unstable_opts.sanitizer.contains(SanitizerSet::ADDRESS) {
"asan/"
@@ -2717,12 +2729,12 @@ fn add_apple_sdk(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) {
let llvm_target = &sess.target.llvm_target;
if sess.target.vendor != "apple"
|| !matches!(os.as_ref(), "ios" | "tvos" | "watchos" | "macos")
|| (flavor != LinkerFlavor::Gcc && flavor != LinkerFlavor::Lld(LldFlavor::Ld64))
|| !matches!(flavor, LinkerFlavor::Darwin(..))
{
return;
}

if os == "macos" && flavor != LinkerFlavor::Lld(LldFlavor::Ld64) {
if os == "macos" && !matches!(flavor, LinkerFlavor::Darwin(Cc::No, _)) {
return;
}

@@ -2756,10 +2768,10 @@ fn add_apple_sdk(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) {
};

match flavor {
LinkerFlavor::Gcc => {
LinkerFlavor::Darwin(Cc::Yes, _) => {
cmd.args(&["-isysroot", &sdk_root, "-Wl,-syslibroot", &sdk_root]);
}
LinkerFlavor::Lld(LldFlavor::Ld64) => {
LinkerFlavor::Darwin(Cc::No, _) => {
cmd.args(&["-syslibroot", &sdk_root]);
}
_ => unreachable!(),
@@ -2822,7 +2834,10 @@ fn get_apple_sdk_root(sdk_name: &str) -> Result<String, String> {

fn add_gcc_ld_path(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) {
if let Some(ld_impl) = sess.opts.unstable_opts.gcc_ld {
if let LinkerFlavor::Gcc = flavor {
if let LinkerFlavor::Gnu(Cc::Yes, _)
| LinkerFlavor::Darwin(Cc::Yes, _)
| LinkerFlavor::WasmLld(Cc::Yes) = flavor
{
match ld_impl {
LdImpl::Lld => {
// Implement the "self-contained" part of -Zgcc-ld
@@ -2837,7 +2852,7 @@ fn add_gcc_ld_path(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) {
// Implement the "linker flavor" part of -Zgcc-ld
// by asking cc to use some kind of lld.
cmd.arg("-fuse-ld=lld");
if sess.target.lld_flavor != LldFlavor::Ld {
if !flavor.is_gnu() {
// Tell clang to use a non-default LLD flavor.
// Gcc doesn't understand the target option, but we currently assume
// that gcc is not used for Apple and Wasm targets (#97402).
82 changes: 37 additions & 45 deletions compiler/rustc_codegen_ssa/src/back/linker.rs
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ use rustc_middle::middle::exported_symbols::{ExportedSymbol, SymbolExportInfo, S
use rustc_middle::ty::TyCtxt;
use rustc_session::config::{self, CrateType, DebugInfo, LinkerPluginLto, Lto, OptLevel, Strip};
use rustc_session::Session;
use rustc_target::spec::{LinkOutputKind, LinkerFlavor, LldFlavor};
use rustc_target::spec::{Cc, LinkOutputKind, LinkerFlavor, Lld};

use cc::windows_registry;

@@ -56,8 +56,13 @@ pub fn get_linker<'a>(
let mut cmd = match linker.to_str() {
Some(linker) if cfg!(windows) && linker.ends_with(".bat") => Command::bat_script(linker),
_ => match flavor {
LinkerFlavor::Lld(f) => Command::lld(linker, f),
LinkerFlavor::Msvc if sess.opts.cg.linker.is_none() && sess.target.linker.is_none() => {
LinkerFlavor::Gnu(Cc::No, Lld::Yes)
| LinkerFlavor::Darwin(Cc::No, Lld::Yes)
| LinkerFlavor::WasmLld(Cc::No)
| LinkerFlavor::Msvc(Lld::Yes) => Command::lld(linker, flavor.lld_flavor()),
LinkerFlavor::Msvc(Lld::No)
if sess.opts.cg.linker.is_none() && sess.target.linker.is_none() =>
{
Command::new(msvc_tool.as_ref().map_or(linker, |t| t.path()))
}
_ => Command::new(linker),
@@ -68,9 +73,7 @@ pub fn get_linker<'a>(
// To comply with the Windows App Certification Kit,
// MSVC needs to link with the Store versions of the runtime libraries (vcruntime, msvcrt, etc).
let t = &sess.target;
if (flavor == LinkerFlavor::Msvc || flavor == LinkerFlavor::Lld(LldFlavor::Link))
&& t.vendor == "uwp"
{
if matches!(flavor, LinkerFlavor::Msvc(..)) && t.vendor == "uwp" {
if let Some(ref tool) = msvc_tool {
let original_path = tool.path();
if let Some(ref root_lib_path) = original_path.ancestors().nth(4) {
@@ -126,23 +129,22 @@ pub fn get_linker<'a>(
// to the linker args construction.
assert!(cmd.get_args().is_empty() || sess.target.vendor == "uwp");
match flavor {
LinkerFlavor::Gcc => {
Box::new(GccLinker { cmd, sess, target_cpu, hinted_static: false, is_ld: false })
as Box<dyn Linker>
}
LinkerFlavor::Ld if sess.target.os == "l4re" => {
LinkerFlavor::Unix(Cc::No) if sess.target.os == "l4re" => {
Box::new(L4Bender::new(cmd, sess)) as Box<dyn Linker>
}
LinkerFlavor::Lld(LldFlavor::Ld)
| LinkerFlavor::Lld(LldFlavor::Ld64)
| LinkerFlavor::Ld => {
Box::new(GccLinker { cmd, sess, target_cpu, hinted_static: false, is_ld: true })
as Box<dyn Linker>
}
LinkerFlavor::Lld(LldFlavor::Link) | LinkerFlavor::Msvc => {
Box::new(MsvcLinker { cmd, sess }) as Box<dyn Linker>
}
LinkerFlavor::Lld(LldFlavor::Wasm) => Box::new(WasmLd::new(cmd, sess)) as Box<dyn Linker>,
LinkerFlavor::WasmLld(Cc::No) => Box::new(WasmLd::new(cmd, sess)) as Box<dyn Linker>,
LinkerFlavor::Gnu(cc, _)
| LinkerFlavor::Darwin(cc, _)
| LinkerFlavor::WasmLld(cc)
| LinkerFlavor::Unix(cc) => Box::new(GccLinker {
cmd,
sess,
target_cpu,
hinted_static: false,
is_ld: cc == Cc::No,
is_gnu: flavor.is_gnu(),
}) as Box<dyn Linker>,
LinkerFlavor::Msvc(..) => Box::new(MsvcLinker { cmd, sess }) as Box<dyn Linker>,
LinkerFlavor::EmCc => Box::new(EmLinker { cmd, sess }) as Box<dyn Linker>,
LinkerFlavor::Bpf => Box::new(BpfLinker { cmd, sess }) as Box<dyn Linker>,
LinkerFlavor::Ptx => Box::new(PtxLinker { cmd, sess }) as Box<dyn Linker>,
@@ -211,6 +213,7 @@ pub struct GccLinker<'a> {
hinted_static: bool, // Keeps track of the current hinting mode.
// Link as ld
is_ld: bool,
is_gnu: bool,
}

impl<'a> GccLinker<'a> {
@@ -359,7 +362,7 @@ impl<'a> Linker for GccLinker<'a> {
fn set_output_kind(&mut self, output_kind: LinkOutputKind, out_filename: &Path) {
match output_kind {
LinkOutputKind::DynamicNoPicExe => {
if !self.is_ld && self.sess.target.linker_is_gnu {
if !self.is_ld && self.is_gnu {
self.cmd.arg("-no-pie");
}
}
@@ -373,7 +376,7 @@ impl<'a> Linker for GccLinker<'a> {
LinkOutputKind::StaticNoPicExe => {
// `-static` works for both gcc wrapper and ld.
self.cmd.arg("-static");
if !self.is_ld && self.sess.target.linker_is_gnu {
if !self.is_ld && self.is_gnu {
self.cmd.arg("-no-pie");
}
}
@@ -432,31 +435,25 @@ impl<'a> Linker for GccLinker<'a> {
// has -needed-l{} / -needed_library {}
// but we have no way to detect that here.
self.sess.warn("`as-needed` modifier not implemented yet for ld64");
} else if self.sess.target.linker_is_gnu && !self.sess.target.is_like_windows {
} else if self.is_gnu && !self.sess.target.is_like_windows {
self.linker_arg("--no-as-needed");
} else {
self.sess.warn("`as-needed` modifier not supported for current linker");
}
}
self.hint_dynamic();
self.cmd.arg(format!(
"-l{}{lib}",
if verbatim && self.sess.target.linker_is_gnu { ":" } else { "" },
));
self.cmd.arg(format!("-l{}{lib}", if verbatim && self.is_gnu { ":" } else { "" },));
if !as_needed {
if self.sess.target.is_like_osx {
// See above FIXME comment
} else if self.sess.target.linker_is_gnu && !self.sess.target.is_like_windows {
} else if self.is_gnu && !self.sess.target.is_like_windows {
self.linker_arg("--as-needed");
}
}
}
fn link_staticlib(&mut self, lib: &str, verbatim: bool) {
self.hint_static();
self.cmd.arg(format!(
"-l{}{lib}",
if verbatim && self.sess.target.linker_is_gnu { ":" } else { "" },
));
self.cmd.arg(format!("-l{}{lib}", if verbatim && self.is_gnu { ":" } else { "" },));
}
fn link_rlib(&mut self, lib: &Path) {
self.hint_static();
@@ -511,10 +508,7 @@ impl<'a> Linker for GccLinker<'a> {
let target = &self.sess.target;
if !target.is_like_osx {
self.linker_arg("--whole-archive");
self.cmd.arg(format!(
"-l{}{lib}",
if verbatim && self.sess.target.linker_is_gnu { ":" } else { "" },
));
self.cmd.arg(format!("-l{}{lib}", if verbatim && self.is_gnu { ":" } else { "" },));
self.linker_arg("--no-whole-archive");
} else {
// -force_load is the macOS equivalent of --whole-archive, but it
@@ -559,21 +553,19 @@ impl<'a> Linker for GccLinker<'a> {
// 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 (self.sess.target.linker_is_gnu || self.sess.target.is_like_wasm)
&& !keep_metadata
{
} else if (self.is_gnu || self.sess.target.is_like_wasm) && !keep_metadata {
self.linker_arg("--gc-sections");
}
}

fn no_gc_sections(&mut self) {
if self.sess.target.linker_is_gnu || self.sess.target.is_like_wasm {
if self.is_gnu || self.sess.target.is_like_wasm {
self.linker_arg("--no-gc-sections");
}
}

fn optimize(&mut self) {
if !self.sess.target.linker_is_gnu && !self.sess.target.is_like_wasm {
if !self.is_gnu && !self.sess.target.is_like_wasm {
return;
}

@@ -587,7 +579,7 @@ impl<'a> Linker for GccLinker<'a> {
}

fn pgo_gen(&mut self) {
if !self.sess.target.linker_is_gnu {
if !self.is_gnu {
return;
}

@@ -758,13 +750,13 @@ impl<'a> Linker for GccLinker<'a> {
fn add_no_exec(&mut self) {
if self.sess.target.is_like_windows {
self.linker_arg("--nxcompat");
} else if self.sess.target.linker_is_gnu {
} else if self.is_gnu {
self.linker_arg("-znoexecstack");
}
}

fn add_as_needed(&mut self) {
if self.sess.target.linker_is_gnu && !self.sess.target.is_like_windows {
if self.is_gnu && !self.sess.target.is_like_windows {
self.linker_arg("--as-needed");
} else if self.sess.target.is_like_solaris {
// -z ignore is the Solaris equivalent to the GNU ld --as-needed option
Loading