Auto merge of #52101 - japaric:linker-flavor, r=alexcrichton

try to infer linker flavor from linker name and vice versa

This is a second take on PR #50359 that implements the logic proposed in https://github.com/rust-lang/rust/pull/50359#pullrequestreview-116663121

With this change it would become possible to link `thumb*` binaries using GNU's LD on stable as `-C linker=arm-none-eabi-ld` would be enough to change both the linker and the linker flavor from their default values of `arm-none-eabi-gcc` and `gcc`.

To link `thumb*` binaries using rustc's LLD on stable `-Z linker-flavor` would need to be stabilized as `-C linker=rust-lld -Z linker-flavor=ld.lld` are both required to change the linker and the linker flavor, but this PR doesn't propose that. We would probably need some sort of stability guarantee around `rust-lld`'s name and availability to make linking with rustc's LLD truly stable.

With this change it would also be possible to link `thumb*` binaries using a system installed LLD on stable using the `-C linker=ld.lld` flag (provided that `ld.lld` is a symlink to the system installed LLD).

r? @alexcrichton
diff --git a/src/librustc/session/mod.rs b/src/librustc/session/mod.rs
index 78f7de0..ef81cd3 100644
--- a/src/librustc/session/mod.rs
+++ b/src/librustc/session/mod.rs
@@ -40,7 +40,7 @@
 use syntax_pos::{MultiSpan, Span};
 use util::profiling::SelfProfiler;
 
-use rustc_target::spec::{LinkerFlavor, PanicStrategy};
+use rustc_target::spec::PanicStrategy;
 use rustc_target::spec::{Target, TargetTriple};
 use rustc_data_structures::flock;
 use jobserver::Client;
@@ -602,13 +602,6 @@
             .panic
             .unwrap_or(self.target.target.options.panic_strategy)
     }
-    pub fn linker_flavor(&self) -> LinkerFlavor {
-        self.opts
-            .debugging_opts
-            .linker_flavor
-            .unwrap_or(self.target.target.linker_flavor)
-    }
-
     pub fn fewer_names(&self) -> bool {
         let more_names = self.opts
             .output_types
diff --git a/src/librustc_codegen_llvm/back/link.rs b/src/librustc_codegen_llvm/back/link.rs
index 37c9993..34ddfa9 100644
--- a/src/librustc_codegen_llvm/back/link.rs
+++ b/src/librustc_codegen_llvm/back/link.rs
@@ -53,7 +53,9 @@
 // The third parameter is for env vars, used on windows to set up the
 // path for MSVC to find its DLLs, and gcc to find its bundled
 // toolchain
-pub fn get_linker(sess: &Session) -> (PathBuf, Command) {
+pub fn get_linker(sess: &Session, linker: &Path, flavor: LinkerFlavor) -> (PathBuf, Command) {
+    let msvc_tool = windows_registry::find_tool(&sess.opts.target_triple.triple(), "link.exe");
+
     // If our linker looks like a batch script on Windows then to execute this
     // we'll need to spawn `cmd` explicitly. This is primarily done to handle
     // emscripten where the linker is `emcc.bat` and needs to be spawned as
@@ -62,36 +64,19 @@
     // This worked historically but is needed manually since #42436 (regression
     // was tagged as #42791) and some more info can be found on #44443 for
     // emscripten itself.
-    let cmd = |linker: &Path| {
-        if let Some(linker) = linker.to_str() {
-            if cfg!(windows) && linker.ends_with(".bat") {
-                return Command::bat_script(linker)
-            }
-        }
-        match sess.linker_flavor() {
+    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.target.options.linker.is_none() =>
+            {
+                Command::new(msvc_tool.as_ref().map(|t| t.path()).unwrap_or(linker))
+            },
             _ => Command::new(linker),
-
         }
     };
 
-    let msvc_tool = windows_registry::find_tool(&sess.opts.target_triple.triple(), "link.exe");
-
-    let linker_path = sess.opts.cg.linker.as_ref().map(|s| &**s)
-        .or(sess.target.target.options.linker.as_ref().map(|s| s.as_ref()))
-        .unwrap_or(match sess.linker_flavor() {
-            LinkerFlavor::Msvc => {
-                msvc_tool.as_ref().map(|t| t.path()).unwrap_or("link.exe".as_ref())
-            }
-            LinkerFlavor::Em if cfg!(windows) => "emcc.bat".as_ref(),
-            LinkerFlavor::Em => "emcc".as_ref(),
-            LinkerFlavor::Gcc => "cc".as_ref(),
-            LinkerFlavor::Ld => "ld".as_ref(),
-            LinkerFlavor::Lld(_) => "lld".as_ref(),
-        });
-
-    let mut cmd = cmd(linker_path);
-
     // The compiler's sysroot often has some bundled tools, so add it to the
     // PATH for the child.
     let mut new_path = sess.host_filesearch(PathKind::All)
@@ -118,7 +103,7 @@
     }
     cmd.env("PATH", env::join_paths(new_path).unwrap());
 
-    (linker_path.to_path_buf(), cmd)
+    (linker.to_path_buf(), cmd)
 }
 
 pub fn remove(sess: &Session, path: &Path) {
@@ -608,6 +593,69 @@
     }
 }
 
+pub fn linker_and_flavor(sess: &Session) -> (PathBuf, LinkerFlavor) {
+    fn infer_from(
+        sess: &Session,
+        linker: Option<PathBuf>,
+        flavor: Option<LinkerFlavor>,
+    ) -> Option<(PathBuf, LinkerFlavor)> {
+        match (linker, flavor) {
+            (Some(linker), Some(flavor)) => Some((linker, flavor)),
+            // only the linker flavor is known; use the default linker for the selected flavor
+            (None, Some(flavor)) => Some((PathBuf::from(match flavor {
+                LinkerFlavor::Em  => if cfg!(windows) { "emcc.bat" } else { "emcc" },
+                LinkerFlavor::Gcc => "cc",
+                LinkerFlavor::Ld => "ld",
+                LinkerFlavor::Msvc => "link.exe",
+                LinkerFlavor::Lld(_) => "lld",
+            }), flavor)),
+            (Some(linker), None) => {
+                let stem = linker.file_stem().and_then(|stem| stem.to_str()).unwrap_or_else(|| {
+                    sess.fatal("couldn't extract file stem from specified linker");
+                }).to_owned();
+
+                let flavor = if stem == "emcc" {
+                    LinkerFlavor::Em
+                } else if stem == "gcc" || stem.ends_with("-gcc") {
+                    LinkerFlavor::Gcc
+                } else if stem == "ld" || stem == "ld.lld" || stem.ends_with("-ld") {
+                    LinkerFlavor::Ld
+                } else if stem == "link" || stem == "lld-link" {
+                    LinkerFlavor::Msvc
+                } else if stem == "lld" || stem == "rust-lld" {
+                    LinkerFlavor::Lld(sess.target.target.options.lld_flavor)
+                } else {
+                    // fall back to the value in the target spec
+                    sess.target.target.linker_flavor
+                };
+
+                Some((linker, flavor))
+            },
+            (None, None) => None,
+        }
+    }
+
+    // linker and linker flavor specified via command line have precedence over what the target
+    // specification specifies
+    if let Some(ret) = infer_from(
+        sess,
+        sess.opts.cg.linker.clone(),
+        sess.opts.debugging_opts.linker_flavor,
+    ) {
+        return ret;
+    }
+
+    if let Some(ret) = infer_from(
+        sess,
+        sess.target.target.options.linker.clone().map(PathBuf::from),
+        Some(sess.target.target.linker_flavor),
+    ) {
+        return ret;
+    }
+
+    bug!("Not enough information provided to determine how to invoke the linker");
+}
+
 // Create a dynamic library or executable
 //
 // This will invoke the system linker/cc to create the resulting file. This
@@ -618,10 +666,10 @@
                  codegen_results: &CodegenResults,
                  tmpdir: &Path) {
     info!("preparing {:?} to {:?}", crate_type, out_filename);
-    let flavor = sess.linker_flavor();
+    let (linker, flavor) = linker_and_flavor(sess);
 
     // The invocations of cc share some flags across platforms
-    let (pname, mut cmd) = get_linker(sess);
+    let (pname, mut cmd) = get_linker(sess, &linker, flavor);
 
     let root = sess.target_filesearch(PathKind::Native).get_lib_path();
     if let Some(args) = sess.target.target.options.pre_link_args.get(&flavor) {
@@ -662,8 +710,8 @@
     }
 
     {
-        let mut linker = codegen_results.linker_info.to_linker(cmd, &sess);
-        link_args(&mut *linker, sess, crate_type, tmpdir,
+        let mut linker = codegen_results.linker_info.to_linker(cmd, &sess, flavor);
+        link_args(&mut *linker, flavor, sess, crate_type, tmpdir,
                   out_filename, codegen_results);
         cmd = linker.finalize();
     }
@@ -735,7 +783,7 @@
         // 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.target.options.linker_is_gnu &&
-           sess.linker_flavor() != LinkerFlavor::Ld &&
+           flavor != LinkerFlavor::Ld &&
            (out.contains("unrecognized command line option") ||
             out.contains("unknown argument")) &&
            out.contains("-no-pie") &&
@@ -984,6 +1032,7 @@
 }
 
 fn link_args(cmd: &mut dyn Linker,
+             flavor: LinkerFlavor,
              sess: &Session,
              crate_type: config::CrateType,
              tmpdir: &Path,
@@ -1068,7 +1117,7 @@
             // independent executables by default. We have to pass -no-pie to
             // explicitly turn that off. Not applicable to ld.
             if sess.target.target.options.linker_is_gnu
-                && sess.linker_flavor() != LinkerFlavor::Ld {
+                && flavor != LinkerFlavor::Ld {
                 cmd.no_position_independent_executable();
             }
         }
diff --git a/src/librustc_codegen_llvm/back/linker.rs b/src/librustc_codegen_llvm/back/linker.rs
index 6311ab7..a429e8f 100644
--- a/src/librustc_codegen_llvm/back/linker.rs
+++ b/src/librustc_codegen_llvm/back/linker.rs
@@ -44,8 +44,9 @@
 
     pub fn to_linker<'a>(&'a self,
                          cmd: Command,
-                         sess: &'a Session) -> Box<dyn Linker+'a> {
-        match sess.linker_flavor() {
+                         sess: &'a Session,
+                         flavor: LinkerFlavor) -> Box<dyn Linker+'a> {
+        match flavor {
             LinkerFlavor::Lld(LldFlavor::Link) |
             LinkerFlavor::Msvc => {
                 Box::new(MsvcLinker {
diff --git a/src/librustc_codegen_llvm/back/write.rs b/src/librustc_codegen_llvm/back/write.rs
index b62836e..462a2c9 100644
--- a/src/librustc_codegen_llvm/back/write.rs
+++ b/src/librustc_codegen_llvm/back/write.rs
@@ -1502,7 +1502,9 @@
 
     let assembler_cmd = if modules_config.no_integrated_as {
         // HACK: currently we use linker (gcc) as our assembler
-        let (name, mut cmd) = get_linker(sess);
+        let (linker, flavor) = link::linker_and_flavor(sess);
+
+        let (name, mut cmd) = get_linker(sess, &linker, flavor);
         cmd.args(&sess.target.target.options.asm_args);
         Some(Arc::new(AssemblerCommand {
             name,
diff --git a/src/librustc_target/spec/mod.rs b/src/librustc_target/spec/mod.rs
index 3a18c56..4a8ae69 100644
--- a/src/librustc_target/spec/mod.rs
+++ b/src/librustc_target/spec/mod.rs
@@ -94,6 +94,29 @@
     Link,
 }
 
+impl LldFlavor {
+    fn from_str(s: &str) -> Option<Self> {
+        Some(match s {
+            "darwin" => LldFlavor::Ld64,
+            "gnu" => LldFlavor::Ld,
+            "link" => LldFlavor::Link,
+            "wasm" => LldFlavor::Wasm,
+            _ => return None,
+        })
+    }
+}
+
+impl ToJson for LldFlavor {
+    fn to_json(&self) -> Json {
+        match *self {
+            LldFlavor::Ld64 => "darwin",
+            LldFlavor::Ld => "gnu",
+            LldFlavor::Link => "link",
+            LldFlavor::Wasm => "wasm",
+        }.to_json()
+    }
+}
+
 impl ToJson for LinkerFlavor {
     fn to_json(&self) -> Json {
         self.desc().to_json()
@@ -437,6 +460,9 @@
     /// Linker to invoke
     pub linker: Option<String>,
 
+    /// LLD flavor
+    pub lld_flavor: LldFlavor,
+
     /// Linker arguments that are passed *before* any user-defined libraries.
     pub pre_link_args: LinkArgs, // ... unconditionally
     pub pre_link_args_crt: LinkArgs, // ... when linking with a bundled crt
@@ -654,6 +680,7 @@
         TargetOptions {
             is_builtin: false,
             linker: option_env!("CFG_DEFAULT_LINKER").map(|s| s.to_string()),
+            lld_flavor: LldFlavor::Ld,
             pre_link_args: LinkArgs::new(),
             pre_link_args_crt: LinkArgs::new(),
             post_link_args: LinkArgs::new(),
@@ -856,6 +883,20 @@
                         .map(|s| s.to_string() );
                 }
             } );
+            ($key_name:ident, LldFlavor) => ( {
+                let name = (stringify!($key_name)).replace("_", "-");
+                obj.find(&name[..]).and_then(|o| o.as_string().and_then(|s| {
+                    if let Some(flavor) = LldFlavor::from_str(&s) {
+                        base.options.$key_name = flavor;
+                    } else {
+                        return Some(Err(format!(
+                            "'{}' is not a valid value for lld-flavor. \
+                             Use 'darwin', 'gnu', 'link' or 'wasm.",
+                            s)))
+                    }
+                    Some(Ok(()))
+                })).unwrap_or(Ok(()))
+            } );
             ($key_name:ident, LinkerFlavor) => ( {
                 let name = (stringify!($key_name)).replace("_", "-");
                 obj.find(&name[..]).and_then(|o| o.as_string().map(|s| {
@@ -911,6 +952,7 @@
 
         key!(is_builtin, bool);
         key!(linker, optional);
+        try!(key!(lld_flavor, LldFlavor));
         key!(pre_link_args, link_args);
         key!(pre_link_args_crt, link_args);
         key!(pre_link_objects_exe, list);
@@ -1118,6 +1160,7 @@
 
         target_option_val!(is_builtin);
         target_option_val!(linker);
+        target_option_val!(lld_flavor);
         target_option_val!(link_args - pre_link_args);
         target_option_val!(link_args - pre_link_args_crt);
         target_option_val!(pre_link_objects_exe);
diff --git a/src/librustc_target/spec/wasm32_unknown_unknown.rs b/src/librustc_target/spec/wasm32_unknown_unknown.rs
index 51d402e..94e7739 100644
--- a/src/librustc_target/spec/wasm32_unknown_unknown.rs
+++ b/src/librustc_target/spec/wasm32_unknown_unknown.rs
@@ -53,6 +53,7 @@
 
         // we use the LLD shipped with the Rust toolchain by default
         linker: Some("rust-lld".to_owned()),
+        lld_flavor: LldFlavor::Wasm,
 
         .. Default::default()
     };