about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
authorGuillaume Gomez <guillaume1.gomez@gmail.com>2024-07-01 20:29:56 +0200
committerGitHub <noreply@github.com>2024-07-01 20:29:56 +0200
commit6b2e6442187e9621693260cb4f2887f8afe55baa (patch)
tree1b726885b1a856cbd9158b80337312cc7bedc4e9 /compiler
parentc4baa3f2f470596a45ebe112cae26ca5a8fae792 (diff)
parent5f9a0d3844e5a00f2c821c9b1e7d17679b758036 (diff)
downloadrust-6b2e6442187e9621693260cb4f2887f8afe55baa.tar.gz
rust-6b2e6442187e9621693260cb4f2887f8afe55baa.zip
Rollup merge of #126832 - petrochenkov:linkarg, r=jieyouxu
linker: Refactor interface for passing arguments to linker

Separate arguments into passed to the underlying linker, to cc wrapper, or supported by both.
Also avoid allocations in all the argument passing functions.

The interfaces would look nicer if not the limitations on returning `&mut Self` in `dyn`-compatible traits, and unnecessary conflicts between `Trait` and `dyn Trait` methods.

try-job: armhf-gnu
try-job: aarch64-gnu
try-job: dist-x86_64-linux
try-job: x86_64-msvc
try-job: i686-msvc
try-job: dist-x86_64-apple
try-job: test-various
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_codegen_ssa/src/back/link.rs75
-rw-r--r--compiler/rustc_codegen_ssa/src/back/linker.rs617
-rw-r--r--compiler/rustc_codegen_ssa/src/back/rpath.rs6
-rw-r--r--compiler/rustc_codegen_ssa/src/back/rpath/tests.rs3
4 files changed, 318 insertions, 383 deletions
diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs
index d509e4ce56d..da4fa41e2aa 100644
--- a/compiler/rustc_codegen_ssa/src/back/link.rs
+++ b/compiler/rustc_codegen_ssa/src/back/link.rs
@@ -45,7 +45,7 @@ use tempfile::Builder as TempFileBuilder;
 
 use itertools::Itertools;
 use std::collections::BTreeSet;
-use std::ffi::{OsStr, OsString};
+use std::ffi::OsString;
 use std::fs::{read, File, OpenOptions};
 use std::io::{BufWriter, Write};
 use std::ops::Deref;
@@ -1306,12 +1306,12 @@ fn link_sanitizer_runtime(
         let filename = format!("rustc{channel}_rt.{name}");
         let path = find_sanitizer_runtime(sess, &filename);
         let rpath = path.to_str().expect("non-utf8 component in path");
-        linker.args(&["-Wl,-rpath", "-Xlinker", rpath]);
+        linker.cc_args(&["-Wl,-rpath", "-Xlinker", rpath]);
         linker.link_dylib_by_name(&filename, false, true);
     } else if sess.target.is_like_msvc && flavor == LinkerFlavor::Msvc(Lld::No) && name == "asan" {
         // MSVC provides the `/INFERASANLIBS` argument to automatically find the
         // compatible ASAN library.
-        linker.arg("/INFERASANLIBS");
+        linker.link_arg("/INFERASANLIBS");
     } else {
         let filename = format!("librustc{channel}_rt.{name}.a");
         let path = find_sanitizer_runtime(sess, &filename).join(&filename);
@@ -1888,9 +1888,9 @@ fn add_post_link_objects(
 /// FIXME: Determine where exactly these args need to be inserted.
 fn add_pre_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) {
     if let Some(args) = sess.target.pre_link_args.get(&flavor) {
-        cmd.args(args.iter().map(Deref::deref));
+        cmd.verbatim_args(args.iter().map(Deref::deref));
     }
-    cmd.args(&sess.opts.unstable_opts.pre_link_args);
+    cmd.verbatim_args(&sess.opts.unstable_opts.pre_link_args);
 }
 
 /// Add a link script embedded in the target, if applicable.
@@ -1908,8 +1908,7 @@ fn add_link_script(cmd: &mut dyn Linker, sess: &Session, tmpdir: &Path, crate_ty
                 sess.dcx().emit_fatal(errors::LinkScriptWriteFailure { path, error });
             }
 
-            cmd.arg("--script");
-            cmd.arg(path);
+            cmd.link_arg("--script").link_arg(path);
         }
         _ => {}
     }
@@ -1918,7 +1917,7 @@ fn add_link_script(cmd: &mut dyn Linker, sess: &Session, tmpdir: &Path, crate_ty
 /// Add arbitrary "user defined" args defined from command line.
 /// FIXME: Determine where exactly these args need to be inserted.
 fn add_user_defined_link_args(cmd: &mut dyn Linker, sess: &Session) {
-    cmd.args(&sess.opts.cg.link_args);
+    cmd.verbatim_args(&sess.opts.cg.link_args);
 }
 
 /// Add arbitrary "late link" args defined by the target spec.
@@ -1936,15 +1935,15 @@ fn add_late_link_args(
         });
     if any_dynamic_crate {
         if let Some(args) = sess.target.late_link_args_dynamic.get(&flavor) {
-            cmd.args(args.iter().map(Deref::deref));
+            cmd.verbatim_args(args.iter().map(Deref::deref));
         }
     } else {
         if let Some(args) = sess.target.late_link_args_static.get(&flavor) {
-            cmd.args(args.iter().map(Deref::deref));
+            cmd.verbatim_args(args.iter().map(Deref::deref));
         }
     }
     if let Some(args) = sess.target.late_link_args.get(&flavor) {
-        cmd.args(args.iter().map(Deref::deref));
+        cmd.verbatim_args(args.iter().map(Deref::deref));
     }
 }
 
@@ -1952,7 +1951,7 @@ fn add_late_link_args(
 /// FIXME: Determine where exactly these args need to be inserted.
 fn add_post_link_args(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) {
     if let Some(args) = sess.target.post_link_args.get(&flavor) {
-        cmd.args(args.iter().map(Deref::deref));
+        cmd.verbatim_args(args.iter().map(Deref::deref));
     }
 }
 
@@ -2097,6 +2096,10 @@ fn add_rpath_args(
     codegen_results: &CodegenResults,
     out_filename: &Path,
 ) {
+    if !sess.target.has_rpath {
+        return;
+    }
+
     // FIXME (#2397): At some point we want to rpath our guesses as to
     // where extern libraries might live, based on the
     // add_lib_search_paths
@@ -2115,11 +2118,10 @@ fn add_rpath_args(
         let rpath_config = RPathConfig {
             libs: &*libs,
             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_flavor.is_gnu(),
         };
-        cmd.args(&rpath::get_rpath_flags(&rpath_config));
+        cmd.cc_args(&rpath::get_rpath_flags(&rpath_config));
     }
 }
 
@@ -2378,7 +2380,7 @@ fn add_order_independent_options(
         } else {
             ""
         };
-        cmd.arg(format!("--dynamic-linker={prefix}ld.so.1"));
+        cmd.link_arg(format!("--dynamic-linker={prefix}ld.so.1"));
     }
 
     if sess.target.eh_frame_header {
@@ -2393,8 +2395,7 @@ fn add_order_independent_options(
     }
 
     if sess.target.os == "emscripten" {
-        cmd.arg("-s");
-        cmd.arg(if sess.panic_strategy() == PanicStrategy::Abort {
+        cmd.cc_arg("-s").cc_arg(if sess.panic_strategy() == PanicStrategy::Abort {
             "DISABLE_EXCEPTION_CATCHING=1"
         } else {
             "DISABLE_EXCEPTION_CATCHING=0"
@@ -2402,22 +2403,21 @@ fn add_order_independent_options(
     }
 
     if flavor == LinkerFlavor::Llbc {
-        cmd.arg("--target");
-        cmd.arg(sess.target.llvm_target.as_ref());
-        cmd.arg("--target-cpu");
-        cmd.arg(&codegen_results.crate_info.target_cpu);
+        cmd.link_args(&[
+            "--target",
+            sess.target.llvm_target.as_ref(),
+            "--target-cpu",
+            &codegen_results.crate_info.target_cpu,
+        ]);
     } else if flavor == LinkerFlavor::Ptx {
-        cmd.arg("--fallback-arch");
-        cmd.arg(&codegen_results.crate_info.target_cpu);
+        cmd.link_args(&["--fallback-arch", &codegen_results.crate_info.target_cpu]);
     } else if flavor == LinkerFlavor::Bpf {
-        cmd.arg("--cpu");
-        cmd.arg(&codegen_results.crate_info.target_cpu);
+        cmd.link_args(&["--cpu", &codegen_results.crate_info.target_cpu]);
         if let Some(feat) = [sess.opts.cg.target_feature.as_str(), &sess.target.options.features]
             .into_iter()
             .find(|feat| !feat.is_empty())
         {
-            cmd.arg("--cpu-features");
-            cmd.arg(feat);
+            cmd.link_args(&["--cpu-features", feat]);
         }
     }
 
@@ -2618,7 +2618,11 @@ fn add_native_libs_from_crate(
             NativeLibKind::WasmImportModule => {}
             NativeLibKind::LinkArg => {
                 if link_static {
-                    cmd.linker_arg(OsStr::new(name), verbatim);
+                    if verbatim {
+                        cmd.verbatim_arg(name);
+                    } else {
+                        cmd.link_arg(name);
+                    }
                 }
             }
         }
@@ -3012,10 +3016,10 @@ fn add_apple_sdk(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) {
             // This is admittedly a bit strange, as on most targets
             // `-isysroot` only applies to include header files, but on Apple
             // targets this also applies to libraries and frameworks.
-            cmd.args(&["-isysroot", &sdk_root]);
+            cmd.cc_args(&["-isysroot", &sdk_root]);
         }
         LinkerFlavor::Darwin(Cc::No, _) => {
-            cmd.args(&["-syslibroot", &sdk_root]);
+            cmd.link_args(&["-syslibroot", &sdk_root]);
         }
         _ => unreachable!(),
     }
@@ -3026,8 +3030,9 @@ fn add_apple_sdk(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) {
         // search path.
 
         // The flags are called `-L` and `-F` both in Clang, ld64 and ldd.
-        cmd.arg(format!("-L{sdk_root}/System/iOSSupport/usr/lib"));
-        cmd.arg(format!("-F{sdk_root}/System/iOSSupport/System/Library/Frameworks"));
+        let sdk_root = Path::new(&sdk_root);
+        cmd.include_path(&sdk_root.join("System/iOSSupport/usr/lib"));
+        cmd.framework_path(&sdk_root.join("System/iOSSupport/System/Library/Frameworks"));
     }
 }
 
@@ -3142,7 +3147,7 @@ fn add_lld_args(
         for path in sess.get_tools_search_paths(false) {
             let linker_path = path.join("gcc-ld");
             linker_path_exists |= linker_path.exists();
-            cmd.arg({
+            cmd.cc_arg({
                 let mut arg = OsString::from("-B");
                 arg.push(linker_path);
                 arg
@@ -3162,7 +3167,7 @@ fn add_lld_args(
     // is to use LLD but the `wasm32-wasip2` target relies on a wrapper around
     // this, `wasm-component-ld`, which is overridden if this option is passed.
     if !sess.target.is_like_wasm {
-        cmd.arg("-fuse-ld=lld");
+        cmd.cc_arg("-fuse-ld=lld");
     }
 
     if !flavor.is_gnu() {
@@ -3186,7 +3191,7 @@ fn add_lld_args(
         // targeting a different linker flavor on macOS, and that's also always
         // the case when targeting WASM.
         if sess.target.linker_flavor != sess.host.linker_flavor {
-            cmd.arg(format!("--target={}", sess.target.llvm_target));
+            cmd.cc_arg(format!("--target={}", sess.target.llvm_target));
         }
     }
 }
diff --git a/compiler/rustc_codegen_ssa/src/back/linker.rs b/compiler/rustc_codegen_ssa/src/back/linker.rs
index a82478900b1..0f75ece9729 100644
--- a/compiler/rustc_codegen_ssa/src/back/linker.rs
+++ b/compiler/rustc_codegen_ssa/src/back/linker.rs
@@ -8,7 +8,7 @@ use std::fs::{self, File};
 use std::io::prelude::*;
 use std::io::{self, BufWriter};
 use std::path::{Path, PathBuf};
-use std::{env, mem, str};
+use std::{env, iter, mem, str};
 
 use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
 use rustc_metadata::find_native_static_library;
@@ -159,6 +159,102 @@ pub fn get_linker<'a>(
     }
 }
 
+// Note: Ideally neither these helper function, nor the macro-generated inherent methods below
+// would exist, and these functions would live in `trait Linker`.
+// Unfortunately, adding these functions to `trait Linker` make it `dyn`-incompatible.
+// If the methods are added to the trait with `where Self: Sized` bounds, then even a separate
+// implementation of them for `dyn Linker {}` wouldn't work due to a conflict with those
+// uncallable methods in the trait.
+
+/// Just pass the arguments to the linker as is.
+/// It is assumed that they are correctly prepared in advance.
+fn verbatim_args<L: Linker + ?Sized>(
+    l: &mut L,
+    args: impl IntoIterator<Item: AsRef<OsStr>>,
+) -> &mut L {
+    for arg in args {
+        l.cmd().arg(arg);
+    }
+    l
+}
+/// Arguments for the underlying linker.
+/// Add options to pass them through cc wrapper if `Linker` is a cc wrapper.
+fn link_args<L: Linker + ?Sized>(
+    l: &mut L,
+    args: impl IntoIterator<Item: AsRef<OsStr>, IntoIter: ExactSizeIterator>,
+) -> &mut L {
+    let args = args.into_iter();
+    if !l.is_cc() {
+        verbatim_args(l, args);
+    } else if args.len() != 0 {
+        // FIXME: Support arguments with commas, see `rpaths_to_flags` for the example.
+        let mut combined_arg = OsString::from("-Wl");
+        for arg in args {
+            combined_arg.push(",");
+            combined_arg.push(arg);
+        }
+        l.cmd().arg(combined_arg);
+    }
+    l
+}
+/// Arguments for the cc wrapper specifically.
+/// Check that it's indeed a cc wrapper and pass verbatim.
+fn cc_args<L: Linker + ?Sized>(l: &mut L, args: impl IntoIterator<Item: AsRef<OsStr>>) -> &mut L {
+    assert!(l.is_cc());
+    verbatim_args(l, args)
+}
+/// Arguments supported by both underlying linker and cc wrapper, pass verbatim.
+fn link_or_cc_args<L: Linker + ?Sized>(
+    l: &mut L,
+    args: impl IntoIterator<Item: AsRef<OsStr>>,
+) -> &mut L {
+    verbatim_args(l, args)
+}
+
+macro_rules! generate_arg_methods {
+    ($($ty:ty)*) => { $(
+        impl $ty {
+            pub fn verbatim_args(&mut self, args: impl IntoIterator<Item: AsRef<OsStr>>) -> &mut Self {
+                verbatim_args(self, args)
+            }
+            pub fn verbatim_arg(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
+                verbatim_args(self, iter::once(arg))
+            }
+            pub fn link_args(&mut self, args: impl IntoIterator<Item: AsRef<OsStr>, IntoIter: ExactSizeIterator>) -> &mut Self {
+                link_args(self, args)
+            }
+            pub fn link_arg(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
+                link_args(self, iter::once(arg))
+            }
+            pub fn cc_args(&mut self, args: impl IntoIterator<Item: AsRef<OsStr>>) -> &mut Self {
+                cc_args(self, args)
+            }
+            pub fn cc_arg(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
+                cc_args(self, iter::once(arg))
+            }
+            pub fn link_or_cc_args(&mut self, args: impl IntoIterator<Item: AsRef<OsStr>>) -> &mut Self {
+                link_or_cc_args(self, args)
+            }
+            pub fn link_or_cc_arg(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
+                link_or_cc_args(self, iter::once(arg))
+            }
+        }
+    )* }
+}
+
+generate_arg_methods! {
+    GccLinker<'_>
+    MsvcLinker<'_>
+    EmLinker<'_>
+    WasmLd<'_>
+    L4Bender<'_>
+    AixLinker<'_>
+    LlbcLinker<'_>
+    PtxLinker<'_>
+    BpfLinker<'_>
+    dyn Linker + '_
+}
+
 /// Linker abstraction used by `back::link` to build up the command to invoke a
 /// linker.
 ///
@@ -168,6 +264,9 @@ pub fn get_linker<'a>(
 /// MSVC linker (e.g., `link.exe`) is being used.
 pub trait Linker {
     fn cmd(&mut self) -> &mut Command;
+    fn is_cc(&self) -> bool {
+        false
+    }
     fn set_output_kind(&mut self, output_kind: LinkOutputKind, out_filename: &Path);
     fn link_dylib_by_name(&mut self, name: &str, verbatim: bool, as_needed: bool);
     fn link_framework_by_name(&mut self, _name: &str, _verbatim: bool, _as_needed: bool) {
@@ -175,10 +274,18 @@ pub trait Linker {
     }
     fn link_staticlib_by_name(&mut self, name: &str, verbatim: bool, whole_archive: bool);
     fn link_staticlib_by_path(&mut self, path: &Path, whole_archive: bool);
-    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 include_path(&mut self, path: &Path) {
+        link_or_cc_args(link_or_cc_args(self, &["-L"]), &[path]);
+    }
+    fn framework_path(&mut self, _path: &Path) {
+        bug!("framework path set with unsupported linker")
+    }
+    fn output_filename(&mut self, path: &Path) {
+        link_or_cc_args(link_or_cc_args(self, &["-o"]), &[path]);
+    }
+    fn add_object(&mut self, path: &Path) {
+        link_or_cc_args(self, &[path]);
+    }
     fn gc_sections(&mut self, keep_metadata: bool);
     fn no_gc_sections(&mut self);
     fn full_relro(&mut self);
@@ -198,25 +305,9 @@ pub trait Linker {
     fn add_no_exec(&mut self) {}
     fn add_as_needed(&mut self) {}
     fn reset_per_library_state(&mut self) {}
-    fn linker_arg(&mut self, arg: &OsStr, verbatim: bool) {
-        self.linker_args(&[arg], verbatim);
-    }
-    fn linker_args(&mut self, args: &[&OsStr], _verbatim: bool) {
-        args.into_iter().for_each(|a| {
-            self.cmd().arg(a);
-        });
-    }
 }
 
 impl dyn Linker + '_ {
-    pub fn arg(&mut self, arg: impl AsRef<OsStr>) {
-        self.cmd().arg(arg);
-    }
-
-    pub fn args(&mut self, args: impl IntoIterator<Item: AsRef<OsStr>>) {
-        self.cmd().args(args);
-    }
-
     pub fn take_cmd(&mut self) -> Command {
         mem::replace(self.cmd(), Command::new(""))
     }
@@ -233,14 +324,6 @@ pub struct GccLinker<'a> {
 }
 
 impl<'a> GccLinker<'a> {
-    fn linker_arg(&mut self, arg: impl AsRef<OsStr>) {
-        Linker::linker_arg(self, arg.as_ref(), false);
-    }
-    fn linker_args(&mut self, args: &[impl AsRef<OsStr>]) {
-        let args_vec: Vec<&OsStr> = args.iter().map(|x| x.as_ref()).collect();
-        Linker::linker_args(self, &args_vec, false);
-    }
-
     fn takes_hints(&self) -> bool {
         // Really this function only returns true if the underlying linker
         // configured for a compiler is binutils `ld.bfd` and `ld.gold`. We
@@ -262,7 +345,7 @@ impl<'a> GccLinker<'a> {
             return;
         }
         if self.hinted_static != Some(true) {
-            self.linker_arg("-Bstatic");
+            self.link_arg("-Bstatic");
             self.hinted_static = Some(true);
         }
     }
@@ -272,7 +355,7 @@ impl<'a> GccLinker<'a> {
             return;
         }
         if self.hinted_static != Some(false) {
-            self.linker_arg("-Bdynamic");
+            self.link_arg("-Bdynamic");
             self.hinted_static = Some(false);
         }
     }
@@ -281,7 +364,7 @@ impl<'a> GccLinker<'a> {
         if let Some(plugin_path) = plugin_path {
             let mut arg = OsString::from("-plugin=");
             arg.push(plugin_path);
-            self.linker_arg(&arg);
+            self.link_arg(&arg);
         }
 
         let opt_level = match self.sess.opts.optimize {
@@ -292,9 +375,9 @@ impl<'a> GccLinker<'a> {
         };
 
         if let Some(path) = &self.sess.opts.unstable_opts.profile_sample_use {
-            self.linker_arg(&format!("-plugin-opt=sample-profile={}", path.display()));
+            self.link_arg(&format!("-plugin-opt=sample-profile={}", path.display()));
         };
-        self.linker_args(&[
+        self.link_args(&[
             &format!("-plugin-opt={opt_level}"),
             &format!("-plugin-opt=mcpu={}", self.target_cpu),
         ]);
@@ -304,10 +387,10 @@ impl<'a> GccLinker<'a> {
         // On mac we need to tell the linker to let this library be rpathed
         if self.sess.target.is_like_osx {
             if !self.is_ld {
-                self.cmd.arg("-dynamiclib");
+                self.cc_arg("-dynamiclib");
             }
 
-            self.linker_arg("-dylib");
+            self.link_arg("-dylib");
 
             // Note that the `osx_rpath_install_name` option here is a hack
             // purely to support rustbuild right now, we should get a more
@@ -316,10 +399,10 @@ impl<'a> GccLinker<'a> {
             if self.sess.opts.cg.rpath || self.sess.opts.unstable_opts.osx_rpath_install_name {
                 let mut rpath = OsString::from("@rpath/");
                 rpath.push(out_filename.file_name().unwrap());
-                self.linker_args(&[OsString::from("-install_name"), rpath]);
+                self.link_arg("-install_name").link_arg(rpath);
             }
         } else {
-            self.cmd.arg("-shared");
+            self.link_or_cc_arg("-shared");
             if self.sess.target.is_like_windows {
                 // The output filename already contains `dll_suffix` so
                 // the resulting import library will have a name in the
@@ -336,7 +419,7 @@ impl<'a> GccLinker<'a> {
                 if let Some(implib_name) = implib_name {
                     let implib = out_filename.parent().map(|dir| dir.join(&implib_name));
                     if let Some(implib) = implib {
-                        self.linker_arg(&format!("--out-implib={}", (*implib).to_str().unwrap()));
+                        self.link_arg(&format!("--out-implib={}", (*implib).to_str().unwrap()));
                     }
                 }
             }
@@ -345,76 +428,56 @@ impl<'a> GccLinker<'a> {
 }
 
 impl<'a> Linker for GccLinker<'a> {
-    /// Passes a series of arguments directly to the linker.
-    ///
-    /// When the linker is ld-like, the arguments are simply appended to the command. When the
-    /// linker is not ld-like such as when using a compiler as a linker, the arguments are joined by
-    /// commas to form an argument that is then prepended with `-Wl`. In this situation, only a
-    /// single argument is appended to the command to ensure that the order of the arguments is
-    /// preserved by the compiler.
-    fn linker_args(&mut self, args: &[&OsStr], verbatim: bool) {
-        if self.is_ld || verbatim {
-            args.into_iter().for_each(|a| {
-                self.cmd.arg(a);
-            });
-        } else {
-            if !args.is_empty() {
-                let mut s = OsString::from("-Wl");
-                for a in args {
-                    s.push(",");
-                    s.push(a);
-                }
-                self.cmd.arg(s);
-            }
-        }
-    }
-
     fn cmd(&mut self) -> &mut Command {
         &mut self.cmd
     }
 
+    fn is_cc(&self) -> bool {
+        !self.is_ld
+    }
+
     fn set_output_kind(&mut self, output_kind: LinkOutputKind, out_filename: &Path) {
         match output_kind {
             LinkOutputKind::DynamicNoPicExe => {
                 if !self.is_ld && self.is_gnu {
-                    self.cmd.arg("-no-pie");
+                    self.cc_arg("-no-pie");
                 }
             }
             LinkOutputKind::DynamicPicExe => {
                 // noop on windows w/ gcc & ld, error w/ lld
                 if !self.sess.target.is_like_windows {
                     // `-pie` works for both gcc wrapper and ld.
-                    self.cmd.arg("-pie");
+                    self.link_or_cc_arg("-pie");
                 }
             }
             LinkOutputKind::StaticNoPicExe => {
                 // `-static` works for both gcc wrapper and ld.
-                self.cmd.arg("-static");
+                self.link_or_cc_arg("-static");
                 if !self.is_ld && self.is_gnu {
-                    self.cmd.arg("-no-pie");
+                    self.cc_arg("-no-pie");
                 }
             }
             LinkOutputKind::StaticPicExe => {
                 if !self.is_ld {
                     // Note that combination `-static -pie` doesn't work as expected
                     // for the gcc wrapper, `-static` in that case suppresses `-pie`.
-                    self.cmd.arg("-static-pie");
+                    self.cc_arg("-static-pie");
                 } else {
                     // `--no-dynamic-linker` and `-z text` are not strictly necessary for producing
                     // a static pie, but currently passed because gcc and clang pass them.
                     // The former suppresses the `INTERP` ELF header specifying dynamic linker,
                     // which is otherwise implicitly injected by ld (but not lld).
                     // The latter doesn't change anything, only ensures that everything is pic.
-                    self.cmd.args(&["-static", "-pie", "--no-dynamic-linker", "-z", "text"]);
+                    self.link_args(&["-static", "-pie", "--no-dynamic-linker", "-z", "text"]);
                 }
             }
             LinkOutputKind::DynamicDylib => self.build_dylib(out_filename),
             LinkOutputKind::StaticDylib => {
-                self.cmd.arg("-static");
+                self.link_or_cc_arg("-static");
                 self.build_dylib(out_filename);
             }
             LinkOutputKind::WasiReactorExe => {
-                self.linker_args(&["--entry", "_initialize"]);
+                self.link_args(&["--entry", "_initialize"]);
             }
         }
         // VxWorks compiler driver introduced `--static-crt` flag specifically for rustc,
@@ -430,7 +493,7 @@ impl<'a> Linker for GccLinker<'a> {
                     | LinkOutputKind::StaticDylib
             )
         {
-            self.cmd.arg("--static-crt");
+            self.cc_arg("--static-crt");
         }
     }
 
@@ -450,18 +513,18 @@ impl<'a> Linker for GccLinker<'a> {
                 // but we have no way to detect that here.
                 self.sess.dcx().emit_warn(errors::Ld64UnimplementedModifier);
             } else if self.is_gnu && !self.sess.target.is_like_windows {
-                self.linker_arg("--no-as-needed");
+                self.link_arg("--no-as-needed");
             } else {
                 self.sess.dcx().emit_warn(errors::LinkerUnsupportedModifier);
             }
         }
         self.hint_dynamic();
-        self.cmd.arg(format!("-l{}{name}", if verbatim && self.is_gnu { ":" } else { "" },));
+        self.link_or_cc_arg(format!("-l{}{name}", if verbatim && self.is_gnu { ":" } else { "" },));
         if !as_needed {
             if self.sess.target.is_like_osx {
                 // See above FIXME comment
             } else if self.is_gnu && !self.sess.target.is_like_windows {
-                self.linker_arg("--as-needed");
+                self.link_arg("--as-needed");
             }
         }
     }
@@ -471,63 +534,51 @@ impl<'a> Linker for GccLinker<'a> {
         if !as_needed {
             // FIXME(81490): ld64 as of macOS 11 supports the -needed_framework
             // flag but we have no way to detect that here.
-            // self.cmd.arg("-needed_framework").arg(name);
+            // self.link_or_cc_arg("-needed_framework").link_or_cc_arg(name);
             self.sess.dcx().emit_warn(errors::Ld64UnimplementedModifier);
         }
-        self.cmd.arg("-framework").arg(name);
+        self.link_or_cc_args(&["-framework", name]);
     }
 
     fn link_staticlib_by_name(&mut self, name: &str, verbatim: bool, whole_archive: bool) {
         self.hint_static();
         let colon = if verbatim && self.is_gnu { ":" } else { "" };
         if !whole_archive {
-            self.cmd.arg(format!("-l{colon}{name}"));
+            self.link_or_cc_arg(format!("-l{colon}{name}"));
         } else if self.sess.target.is_like_osx {
             // -force_load is the macOS equivalent of --whole-archive, but it
             // involves passing the full path to the library to link.
-            self.linker_arg("-force_load");
-            self.linker_arg(find_native_static_library(name, verbatim, self.sess));
+            self.link_arg("-force_load");
+            self.link_arg(find_native_static_library(name, verbatim, self.sess));
         } else {
-            self.linker_arg("--whole-archive");
-            self.cmd.arg(format!("-l{colon}{name}"));
-            self.linker_arg("--no-whole-archive");
+            self.link_arg("--whole-archive")
+                .link_or_cc_arg(format!("-l{colon}{name}"))
+                .link_arg("--no-whole-archive");
         }
     }
 
     fn link_staticlib_by_path(&mut self, path: &Path, whole_archive: bool) {
         self.hint_static();
         if !whole_archive {
-            self.cmd.arg(path);
+            self.link_or_cc_arg(path);
         } else if self.sess.target.is_like_osx {
-            self.linker_arg("-force_load");
-            self.linker_arg(path);
+            self.link_arg("-force_load").link_arg(path);
         } else {
-            self.linker_arg("--whole-archive");
-            self.linker_arg(path);
-            self.linker_arg("--no-whole-archive");
+            self.link_arg("--whole-archive").link_arg(path).link_arg("--no-whole-archive");
         }
     }
 
-    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);
+        self.link_or_cc_arg("-F").link_or_cc_arg(path);
     }
     fn full_relro(&mut self) {
-        self.linker_args(&["-z", "relro", "-z", "now"]);
+        self.link_args(&["-z", "relro", "-z", "now"]);
     }
     fn partial_relro(&mut self) {
-        self.linker_args(&["-z", "relro"]);
+        self.link_args(&["-z", "relro"]);
     }
     fn no_relro(&mut self) {
-        self.linker_args(&["-z", "norelro"]);
+        self.link_args(&["-z", "norelro"]);
     }
 
     fn gc_sections(&mut self, keep_metadata: bool) {
@@ -546,7 +597,7 @@ impl<'a> Linker for GccLinker<'a> {
         // for partial linking when using multiple codegen units (-r). So we
         // insert it here.
         if self.sess.target.is_like_osx {
-            self.linker_arg("-dead_strip");
+            self.link_arg("-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
@@ -554,13 +605,13 @@ impl<'a> Linker for GccLinker<'a> {
         // --gc-sections drops the size of hello world from 1.8MB to 597K, a 67%
         // reduction.
         } else if (self.is_gnu || self.sess.target.is_like_wasm) && !keep_metadata {
-            self.linker_arg("--gc-sections");
+            self.link_arg("--gc-sections");
         }
     }
 
     fn no_gc_sections(&mut self) {
         if self.is_gnu || self.sess.target.is_like_wasm {
-            self.linker_arg("--no-gc-sections");
+            self.link_arg("--no-gc-sections");
         }
     }
 
@@ -574,7 +625,7 @@ impl<'a> Linker for GccLinker<'a> {
         if self.sess.opts.optimize == config::OptLevel::Default
             || self.sess.opts.optimize == config::OptLevel::Aggressive
         {
-            self.linker_arg("-O1");
+            self.link_arg("-O1");
         }
     }
 
@@ -594,8 +645,7 @@ impl<'a> Linker for GccLinker<'a> {
         //
         // Though it may be worth to try to revert those changes upstream, since
         // the overhead of the initialization should be minor.
-        self.cmd.arg("-u");
-        self.cmd.arg("__llvm_profile_runtime");
+        self.link_or_cc_args(&["-u", "__llvm_profile_runtime"]);
     }
 
     fn control_flow_guard(&mut self) {}
@@ -616,33 +666,33 @@ impl<'a> Linker for GccLinker<'a> {
                 // The --strip-debug case is handled by running an external
                 // `strip` utility as a separate step after linking.
                 if !self.sess.target.is_like_solaris {
-                    self.linker_arg("--strip-debug");
+                    self.link_arg("--strip-debug");
                 }
             }
             Strip::Symbols => {
-                self.linker_arg("--strip-all");
+                self.link_arg("--strip-all");
             }
         }
         match self.sess.opts.unstable_opts.debuginfo_compression {
             config::DebugInfoCompression::None => {}
             config::DebugInfoCompression::Zlib => {
-                self.linker_arg("--compress-debug-sections=zlib");
+                self.link_arg("--compress-debug-sections=zlib");
             }
             config::DebugInfoCompression::Zstd => {
-                self.linker_arg("--compress-debug-sections=zstd");
+                self.link_arg("--compress-debug-sections=zstd");
             }
         }
     }
 
     fn no_crt_objects(&mut self) {
         if !self.is_ld {
-            self.cmd.arg("-nostartfiles");
+            self.cc_arg("-nostartfiles");
         }
     }
 
     fn no_default_libraries(&mut self) {
         if !self.is_ld {
-            self.cmd.arg("-nodefaultlibs");
+            self.cc_arg("-nodefaultlibs");
         }
     }
 
@@ -718,24 +768,22 @@ impl<'a> Linker for GccLinker<'a> {
         }
 
         if self.sess.target.is_like_osx {
-            self.linker_args(&[OsString::from("-exported_symbols_list"), path.into()]);
+            self.link_arg("-exported_symbols_list").link_arg(path);
         } else if self.sess.target.is_like_solaris {
-            self.linker_args(&[OsString::from("-M"), path.into()]);
+            self.link_arg("-M").link_arg(path);
         } else {
             if is_windows {
-                self.linker_arg(path);
+                self.link_arg(path);
             } else {
                 let mut arg = OsString::from("--version-script=");
                 arg.push(path);
-                self.linker_arg(arg);
-                self.linker_arg("--no-undefined-version");
+                self.link_arg(arg).link_arg("--no-undefined-version");
             }
         }
     }
 
     fn subsystem(&mut self, subsystem: &str) {
-        self.linker_arg("--subsystem");
-        self.linker_arg(&subsystem);
+        self.link_args(&["--subsystem", subsystem]);
     }
 
     fn reset_per_library_state(&mut self) {
@@ -760,23 +808,23 @@ impl<'a> Linker for GccLinker<'a> {
     // Some versions of `gcc` add it implicitly, some (e.g. `musl-gcc`) don't,
     // so we just always add it.
     fn add_eh_frame_header(&mut self) {
-        self.linker_arg("--eh-frame-hdr");
+        self.link_arg("--eh-frame-hdr");
     }
 
     fn add_no_exec(&mut self) {
         if self.sess.target.is_like_windows {
-            self.linker_arg("--nxcompat");
+            self.link_arg("--nxcompat");
         } else if self.is_gnu {
-            self.linker_args(&["-z", "noexecstack"]);
+            self.link_args(&["-z", "noexecstack"]);
         }
     }
 
     fn add_as_needed(&mut self) {
         if self.is_gnu && !self.sess.target.is_like_windows {
-            self.linker_arg("--as-needed");
+            self.link_arg("--as-needed");
         } else if self.sess.target.is_like_solaris {
             // -z ignore is the Solaris equivalent to the GNU ld --as-needed option
-            self.linker_args(&["-z", "ignore"]);
+            self.link_args(&["-z", "ignore"]);
         }
     }
 }
@@ -798,10 +846,10 @@ impl<'a> Linker for MsvcLinker<'a> {
             | LinkOutputKind::StaticNoPicExe
             | LinkOutputKind::StaticPicExe => {}
             LinkOutputKind::DynamicDylib | LinkOutputKind::StaticDylib => {
-                self.cmd.arg("/DLL");
+                self.link_arg("/DLL");
                 let mut arg: OsString = "/IMPLIB:".into();
                 arg.push(out_filename.with_extension("dll.lib"));
-                self.cmd.arg(arg);
+                self.link_arg(arg);
             }
             LinkOutputKind::WasiReactorExe => {
                 panic!("can't link as reactor on non-wasi target");
@@ -810,44 +858,40 @@ impl<'a> Linker for MsvcLinker<'a> {
     }
 
     fn link_dylib_by_name(&mut self, name: &str, verbatim: bool, _as_needed: bool) {
-        self.cmd.arg(format!("{}{}", name, if verbatim { "" } else { ".lib" }));
+        self.link_arg(format!("{}{}", name, if verbatim { "" } else { ".lib" }));
     }
 
     fn link_staticlib_by_name(&mut self, name: &str, verbatim: bool, whole_archive: bool) {
         let prefix = if whole_archive { "/WHOLEARCHIVE:" } else { "" };
         let suffix = if verbatim { "" } else { ".lib" };
-        self.cmd.arg(format!("{prefix}{name}{suffix}"));
+        self.link_arg(format!("{prefix}{name}{suffix}"));
     }
 
     fn link_staticlib_by_path(&mut self, path: &Path, whole_archive: bool) {
         if !whole_archive {
-            self.cmd.arg(path);
+            self.link_arg(path);
         } else {
             let mut arg = OsString::from("/WHOLEARCHIVE:");
             arg.push(path);
-            self.cmd.arg(arg);
+            self.link_arg(arg);
         }
     }
 
-    fn add_object(&mut self, path: &Path) {
-        self.cmd.arg(path);
-    }
-
     fn gc_sections(&mut self, _keep_metadata: bool) {
         // MSVC's ICF (Identical COMDAT Folding) link optimization is
         // slow for Rust and thus we disable it by default when not in
         // optimization build.
         if self.sess.opts.optimize != config::OptLevel::No {
-            self.cmd.arg("/OPT:REF,ICF");
+            self.link_arg("/OPT:REF,ICF");
         } else {
             // It is necessary to specify NOICF here, because /OPT:REF
             // implies ICF by default.
-            self.cmd.arg("/OPT:REF,NOICF");
+            self.link_arg("/OPT:REF,NOICF");
         }
     }
 
     fn no_gc_sections(&mut self) {
-        self.cmd.arg("/OPT:NOREF,NOICF");
+        self.link_arg("/OPT:NOREF,NOICF");
     }
 
     fn full_relro(&mut self) {
@@ -867,23 +911,19 @@ impl<'a> Linker for MsvcLinker<'a> {
     }
 
     fn no_default_libraries(&mut self) {
-        self.cmd.arg("/NODEFAULTLIB");
+        self.link_arg("/NODEFAULTLIB");
     }
 
     fn include_path(&mut self, path: &Path) {
         let mut arg = OsString::from("/LIBPATH:");
         arg.push(path);
-        self.cmd.arg(&arg);
+        self.link_arg(&arg);
     }
 
     fn output_filename(&mut self, path: &Path) {
         let mut arg = OsString::from("/OUT:");
         arg.push(path);
-        self.cmd.arg(&arg);
-    }
-
-    fn framework_path(&mut self, _path: &Path) {
-        bug!("frameworks are not supported on windows")
+        self.link_arg(&arg);
     }
 
     fn optimize(&mut self) {
@@ -895,19 +935,19 @@ impl<'a> Linker for MsvcLinker<'a> {
     }
 
     fn control_flow_guard(&mut self) {
-        self.cmd.arg("/guard:cf");
+        self.link_arg("/guard:cf");
     }
 
     fn ehcont_guard(&mut self) {
         if self.sess.target.pointer_width == 64 {
-            self.cmd.arg("/guard:ehcont");
+            self.link_arg("/guard:ehcont");
         }
     }
 
     fn debuginfo(&mut self, _strip: Strip, natvis_debugger_visualizers: &[PathBuf]) {
         // This will cause the Microsoft linker to generate a PDB file
         // from the CodeView line tables in the object files.
-        self.cmd.arg("/DEBUG");
+        self.link_arg("/DEBUG");
 
         // Default to emitting only the file name of the PDB file into
         // the binary instead of the full path. Emitting the full path
@@ -916,7 +956,7 @@ impl<'a> Linker for MsvcLinker<'a> {
         //
         // This default behavior can be overridden by explicitly passing
         // `-Clink-arg=/PDBALTPATH:...` to rustc.
-        self.cmd.arg("/PDBALTPATH:%_PDB%");
+        self.link_arg("/PDBALTPATH:%_PDB%");
 
         // This will cause the Microsoft linker to embed .natvis info into the PDB file
         let natvis_dir_path = self.sess.sysroot.join("lib\\rustlib\\etc");
@@ -928,7 +968,7 @@ impl<'a> Linker for MsvcLinker<'a> {
                         if path.extension() == Some("natvis".as_ref()) {
                             let mut arg = OsString::from("/NATVIS:");
                             arg.push(path);
-                            self.cmd.arg(arg);
+                            self.link_arg(arg);
                         }
                     }
                     Err(error) => {
@@ -942,7 +982,7 @@ impl<'a> Linker for MsvcLinker<'a> {
         for path in natvis_debugger_visualizers {
             let mut arg = OsString::from("/NATVIS:");
             arg.push(path);
-            self.cmd.arg(arg);
+            self.link_arg(arg);
         }
     }
 
@@ -986,13 +1026,13 @@ impl<'a> Linker for MsvcLinker<'a> {
         }
         let mut arg = OsString::from("/DEF:");
         arg.push(path);
-        self.cmd.arg(&arg);
+        self.link_arg(&arg);
     }
 
     fn subsystem(&mut self, subsystem: &str) {
         // Note that previous passes of the compiler validated this subsystem,
         // so we just blindly pass it to the linker.
-        self.cmd.arg(&format!("/SUBSYSTEM:{subsystem}"));
+        self.link_arg(&format!("/SUBSYSTEM:{subsystem}"));
 
         // Windows has two subsystems we're interested in right now, the console
         // and windows subsystems. These both implicitly have different entry
@@ -1009,7 +1049,7 @@ impl<'a> Linker for MsvcLinker<'a> {
         //
         // For more information see RFC #1665
         if subsystem == "windows" {
-            self.cmd.arg("/ENTRY:mainCRTStartup");
+            self.link_arg("/ENTRY:mainCRTStartup");
         }
     }
 
@@ -1018,7 +1058,7 @@ impl<'a> Linker for MsvcLinker<'a> {
     }
 
     fn add_no_exec(&mut self) {
-        self.cmd.arg("/NXCOMPAT");
+        self.link_arg("/NXCOMPAT");
     }
 }
 
@@ -1032,31 +1072,23 @@ impl<'a> Linker for EmLinker<'a> {
         &mut self.cmd
     }
 
+    fn is_cc(&self) -> bool {
+        true
+    }
+
     fn set_output_kind(&mut self, _output_kind: LinkOutputKind, _out_filename: &Path) {}
 
     fn link_dylib_by_name(&mut self, name: &str, _verbatim: bool, _as_needed: bool) {
         // Emscripten always links statically
-        self.cmd.arg("-l").arg(name);
+        self.link_or_cc_args(&["-l", name]);
     }
 
     fn link_staticlib_by_name(&mut self, name: &str, _verbatim: bool, _whole_archive: bool) {
-        self.cmd.arg("-l").arg(name);
+        self.link_or_cc_args(&["-l", name]);
     }
 
     fn link_staticlib_by_path(&mut self, path: &Path, _whole_archive: bool) {
-        self.cmd.arg(path);
-    }
-
-    fn include_path(&mut self, path: &Path) {
-        self.cmd.arg("-L").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);
+        self.link_or_cc_arg(path);
     }
 
     fn full_relro(&mut self) {
@@ -1071,10 +1103,6 @@ impl<'a> Linker for EmLinker<'a> {
         // noop
     }
 
-    fn framework_path(&mut self, _path: &Path) {
-        bug!("frameworks are not supported on Emscripten")
-    }
-
     fn gc_sections(&mut self, _keep_metadata: bool) {
         // noop
     }
@@ -1085,7 +1113,7 @@ impl<'a> Linker for EmLinker<'a> {
 
     fn optimize(&mut self) {
         // Emscripten performs own optimizations
-        self.cmd.arg(match self.sess.opts.optimize {
+        self.cc_arg(match self.sess.opts.optimize {
             OptLevel::No => "-O0",
             OptLevel::Less => "-O1",
             OptLevel::Default => "-O2",
@@ -1106,7 +1134,7 @@ impl<'a> Linker for EmLinker<'a> {
     fn debuginfo(&mut self, _strip: Strip, _: &[PathBuf]) {
         // Preserve names or generate source maps depending on debug info
         // For more information see https://emscripten.org/docs/tools_reference/emcc.html#emcc-g
-        self.cmd.arg(match self.sess.opts.debuginfo {
+        self.cc_arg(match self.sess.opts.debuginfo {
             DebugInfo::None => "-g0",
             DebugInfo::Limited | DebugInfo::LineTablesOnly | DebugInfo::LineDirectivesOnly => {
                 "--profiling-funcs"
@@ -1118,13 +1146,13 @@ impl<'a> Linker for EmLinker<'a> {
     fn no_crt_objects(&mut self) {}
 
     fn no_default_libraries(&mut self) {
-        self.cmd.arg("-nodefaultlibs");
+        self.cc_arg("-nodefaultlibs");
     }
 
     fn export_symbols(&mut self, _tmpdir: &Path, _crate_type: CrateType, symbols: &[String]) {
         debug!("EXPORTED SYMBOLS:");
 
-        self.cmd.arg("-s");
+        self.cc_arg("-s");
 
         let mut arg = OsString::from("EXPORTED_FUNCTIONS=");
         let encoded = serde_json::to_string(
@@ -1135,7 +1163,7 @@ impl<'a> Linker for EmLinker<'a> {
 
         arg.push(encoded);
 
-        self.cmd.arg(arg);
+        self.cc_arg(arg);
     }
 
     fn subsystem(&mut self, _subsystem: &str) {
@@ -1153,7 +1181,7 @@ pub struct WasmLd<'a> {
 }
 
 impl<'a> WasmLd<'a> {
-    fn new(mut cmd: Command, sess: &'a Session) -> WasmLd<'a> {
+    fn new(cmd: Command, sess: &'a Session) -> WasmLd<'a> {
         // If the atomics feature is enabled for wasm then we need a whole bunch
         // of flags:
         //
@@ -1172,18 +1200,19 @@ impl<'a> WasmLd<'a> {
         // On wasm32-unknown-unknown, we also export symbols for glue code to use:
         //    * `--export=*tls*` - when `#[thread_local]` symbols are used these
         //      symbols are how the TLS segments are initialized and configured.
+        let mut wasm_ld = WasmLd { cmd, sess };
         if sess.target_features.contains(&sym::atomics) {
-            cmd.arg("--shared-memory");
-            cmd.arg("--max-memory=1073741824");
-            cmd.arg("--import-memory");
+            wasm_ld.link_args(&["--shared-memory", "--max-memory=1073741824", "--import-memory"]);
             if sess.target.os == "unknown" {
-                cmd.arg("--export=__wasm_init_tls");
-                cmd.arg("--export=__tls_size");
-                cmd.arg("--export=__tls_align");
-                cmd.arg("--export=__tls_base");
+                wasm_ld.link_args(&[
+                    "--export=__wasm_init_tls",
+                    "--export=__tls_size",
+                    "--export=__tls_align",
+                    "--export=__tls_base",
+                ]);
             }
         }
-        WasmLd { cmd, sess }
+        wasm_ld
     }
 }
 
@@ -1199,51 +1228,36 @@ impl<'a> Linker for WasmLd<'a> {
             | LinkOutputKind::StaticNoPicExe
             | LinkOutputKind::StaticPicExe => {}
             LinkOutputKind::DynamicDylib | LinkOutputKind::StaticDylib => {
-                self.cmd.arg("--no-entry");
+                self.link_arg("--no-entry");
             }
             LinkOutputKind::WasiReactorExe => {
-                self.cmd.arg("--entry");
-                self.cmd.arg("_initialize");
+                self.link_args(&["--entry", "_initialize"]);
             }
         }
     }
 
     fn link_dylib_by_name(&mut self, name: &str, _verbatim: bool, _as_needed: bool) {
-        self.cmd.arg("-l").arg(name);
+        self.link_or_cc_args(&["-l", name]);
     }
 
     fn link_staticlib_by_name(&mut self, name: &str, _verbatim: bool, whole_archive: bool) {
         if !whole_archive {
-            self.cmd.arg("-l").arg(name);
+            self.link_or_cc_args(&["-l", name]);
         } else {
-            self.cmd.arg("--whole-archive").arg("-l").arg(name).arg("--no-whole-archive");
+            self.link_arg("--whole-archive")
+                .link_or_cc_args(&["-l", name])
+                .link_arg("--no-whole-archive");
         }
     }
 
     fn link_staticlib_by_path(&mut self, path: &Path, whole_archive: bool) {
         if !whole_archive {
-            self.cmd.arg(path);
+            self.link_or_cc_arg(path);
         } else {
-            self.cmd.arg("--whole-archive").arg(path).arg("--no-whole-archive");
+            self.link_arg("--whole-archive").link_or_cc_arg(path).link_arg("--no-whole-archive");
         }
     }
 
-    fn include_path(&mut self, path: &Path) {
-        self.cmd.arg("-L").arg(path);
-    }
-
-    fn framework_path(&mut self, _path: &Path) {
-        panic!("frameworks not supported")
-    }
-
-    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 full_relro(&mut self) {}
 
     fn partial_relro(&mut self) {}
@@ -1251,17 +1265,17 @@ impl<'a> Linker for WasmLd<'a> {
     fn no_relro(&mut self) {}
 
     fn gc_sections(&mut self, _keep_metadata: bool) {
-        self.cmd.arg("--gc-sections");
+        self.link_arg("--gc-sections");
     }
 
     fn no_gc_sections(&mut self) {
-        self.cmd.arg("--no-gc-sections");
+        self.link_arg("--no-gc-sections");
     }
 
     fn optimize(&mut self) {
         // The -O flag is, as of late 2023, only used for merging of strings and debuginfo, and
         // only differentiates -O0 and -O1. It does not apply to LTO.
-        self.cmd.arg(match self.sess.opts.optimize {
+        self.link_arg(match self.sess.opts.optimize {
             OptLevel::No => "-O0",
             OptLevel::Less => "-O1",
             OptLevel::Default => "-O2",
@@ -1279,10 +1293,10 @@ impl<'a> Linker for WasmLd<'a> {
         match strip {
             Strip::None => {}
             Strip::Debuginfo => {
-                self.cmd.arg("--strip-debug");
+                self.link_arg("--strip-debug");
             }
             Strip::Symbols => {
-                self.cmd.arg("--strip-all");
+                self.link_arg("--strip-all");
             }
         }
     }
@@ -1297,7 +1311,7 @@ impl<'a> Linker for WasmLd<'a> {
 
     fn export_symbols(&mut self, _tmpdir: &Path, _crate_type: CrateType, symbols: &[String]) {
         for sym in symbols {
-            self.cmd.arg("--export").arg(&sym);
+            self.link_args(&["--export", sym]);
         }
 
         // LLD will hide these otherwise-internal symbols since it only exports
@@ -1305,8 +1319,7 @@ impl<'a> Linker for WasmLd<'a> {
         // others. Various bits and pieces of wasm32-unknown-unknown tooling use
         // this, so be sure these symbols make their way out of the linker as well.
         if self.sess.target.os == "unknown" {
-            self.cmd.arg("--export=__heap_base");
-            self.cmd.arg("--export=__data_end");
+            self.link_args(&["--export=__heap_base", "--export=__data_end"]);
         }
     }
 
@@ -1337,7 +1350,7 @@ impl<'a> WasmLd<'a> {
             // wasm-ld only handles integer LTO opt levels. Use O2
             config::OptLevel::Size | config::OptLevel::SizeMin => "O2",
         };
-        self.cmd.arg(&format!("--lto-{opt_level}"));
+        self.link_arg(&format!("--lto-{opt_level}"));
     }
 }
 
@@ -1362,56 +1375,43 @@ impl<'a> Linker for L4Bender<'a> {
     fn link_staticlib_by_name(&mut self, name: &str, _verbatim: bool, whole_archive: bool) {
         self.hint_static();
         if !whole_archive {
-            self.cmd.arg(format!("-PC{name}"));
+            self.link_arg(format!("-PC{name}"));
         } else {
-            self.cmd.arg("--whole-archive").arg(format!("-l{name}")).arg("--no-whole-archive");
+            self.link_arg("--whole-archive")
+                .link_or_cc_arg(format!("-l{name}"))
+                .link_arg("--no-whole-archive");
         }
     }
 
     fn link_staticlib_by_path(&mut self, path: &Path, whole_archive: bool) {
         self.hint_static();
         if !whole_archive {
-            self.cmd.arg(path);
+            self.link_or_cc_arg(path);
         } else {
-            self.cmd.arg("--whole-archive").arg(path).arg("--no-whole-archive");
+            self.link_arg("--whole-archive").link_or_cc_arg(path).link_arg("--no-whole-archive");
         }
     }
 
-    fn include_path(&mut self, path: &Path) {
-        self.cmd.arg("-L").arg(path);
-    }
-    fn framework_path(&mut self, _: &Path) {
-        bug!("frameworks are not supported on L4Re");
-    }
-    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 full_relro(&mut self) {
-        self.cmd.arg("-z").arg("relro");
-        self.cmd.arg("-z").arg("now");
+        self.link_args(&["-z", "relro", "-z", "now"]);
     }
 
     fn partial_relro(&mut self) {
-        self.cmd.arg("-z").arg("relro");
+        self.link_args(&["-z", "relro"]);
     }
 
     fn no_relro(&mut self) {
-        self.cmd.arg("-z").arg("norelro");
+        self.link_args(&["-z", "norelro"]);
     }
 
     fn gc_sections(&mut self, keep_metadata: bool) {
         if !keep_metadata {
-            self.cmd.arg("--gc-sections");
+            self.link_arg("--gc-sections");
         }
     }
 
     fn no_gc_sections(&mut self) {
-        self.cmd.arg("--no-gc-sections");
+        self.link_arg("--no-gc-sections");
     }
 
     fn optimize(&mut self) {
@@ -1420,7 +1420,7 @@ impl<'a> Linker for L4Bender<'a> {
         if self.sess.opts.optimize == config::OptLevel::Default
             || self.sess.opts.optimize == config::OptLevel::Aggressive
         {
-            self.cmd.arg("-O1");
+            self.link_arg("-O1");
         }
     }
 
@@ -1430,16 +1430,16 @@ impl<'a> Linker for L4Bender<'a> {
         match strip {
             Strip::None => {}
             Strip::Debuginfo => {
-                self.cmd().arg("--strip-debug");
+                self.link_arg("--strip-debug");
             }
             Strip::Symbols => {
-                self.cmd().arg("--strip-all");
+                self.link_arg("--strip-all");
             }
         }
     }
 
     fn no_default_libraries(&mut self) {
-        self.cmd.arg("-nostdlib");
+        self.cc_arg("-nostdlib");
     }
 
     fn export_symbols(&mut self, _: &Path, _: CrateType, _: &[String]) {
@@ -1449,7 +1449,7 @@ impl<'a> Linker for L4Bender<'a> {
     }
 
     fn subsystem(&mut self, subsystem: &str) {
-        self.cmd.arg(&format!("--subsystem {subsystem}"));
+        self.link_arg(&format!("--subsystem {subsystem}"));
     }
 
     fn reset_per_library_state(&mut self) {
@@ -1467,12 +1467,12 @@ impl<'a> Linker for L4Bender<'a> {
 
 impl<'a> L4Bender<'a> {
     pub fn new(cmd: Command, sess: &'a Session) -> L4Bender<'a> {
-        L4Bender { cmd: cmd, sess: sess, hinted_static: false }
+        L4Bender { cmd, sess: sess, hinted_static: false }
     }
 
     fn hint_static(&mut self) {
         if !self.hinted_static {
-            self.cmd.arg("-static");
+            self.link_or_cc_arg("-static");
             self.hinted_static = true;
         }
     }
@@ -1487,29 +1487,28 @@ pub struct AixLinker<'a> {
 
 impl<'a> AixLinker<'a> {
     pub fn new(cmd: Command, sess: &'a Session) -> AixLinker<'a> {
-        AixLinker { cmd: cmd, sess: sess, hinted_static: None }
+        AixLinker { cmd, sess: sess, hinted_static: None }
     }
 
     fn hint_static(&mut self) {
         if self.hinted_static != Some(true) {
-            self.cmd.arg("-bstatic");
+            self.link_arg("-bstatic");
             self.hinted_static = Some(true);
         }
     }
 
     fn hint_dynamic(&mut self) {
         if self.hinted_static != Some(false) {
-            self.cmd.arg("-bdynamic");
+            self.link_arg("-bdynamic");
             self.hinted_static = Some(false);
         }
     }
 
     fn build_dylib(&mut self, _out_filename: &Path) {
-        self.cmd.arg("-bM:SRE");
-        self.cmd.arg("-bnoentry");
+        self.link_args(&["-bM:SRE", "-bnoentry"]);
         // FIXME: Use CreateExportList utility to create export list
         // and remove -bexpfull.
-        self.cmd.arg("-bexpfull");
+        self.link_arg("-bexpfull");
     }
 }
 
@@ -1534,47 +1533,31 @@ impl<'a> Linker for AixLinker<'a> {
 
     fn link_dylib_by_name(&mut self, name: &str, _verbatim: bool, _as_needed: bool) {
         self.hint_dynamic();
-        self.cmd.arg(format!("-l{name}"));
+        self.link_or_cc_arg(format!("-l{name}"));
     }
 
     fn link_staticlib_by_name(&mut self, name: &str, verbatim: bool, whole_archive: bool) {
         self.hint_static();
         if !whole_archive {
-            self.cmd.arg(format!("-l{name}"));
+            self.link_or_cc_arg(format!("-l{name}"));
         } else {
             let mut arg = OsString::from("-bkeepfile:");
             arg.push(find_native_static_library(name, verbatim, self.sess));
-            self.cmd.arg(arg);
+            self.link_or_cc_arg(arg);
         }
     }
 
     fn link_staticlib_by_path(&mut self, path: &Path, whole_archive: bool) {
         self.hint_static();
         if !whole_archive {
-            self.cmd.arg(path);
+            self.link_or_cc_arg(path);
         } else {
             let mut arg = OsString::from("-bkeepfile:");
             arg.push(path);
-            self.cmd.arg(arg);
+            self.link_arg(arg);
         }
     }
 
-    fn include_path(&mut self, path: &Path) {
-        self.cmd.arg("-L").arg(path);
-    }
-
-    fn framework_path(&mut self, _: &Path) {
-        bug!("frameworks are not supported on AIX");
-    }
-
-    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 full_relro(&mut self) {}
 
     fn partial_relro(&mut self) {}
@@ -1582,17 +1565,17 @@ impl<'a> Linker for AixLinker<'a> {
     fn no_relro(&mut self) {}
 
     fn gc_sections(&mut self, _keep_metadata: bool) {
-        self.cmd.arg("-bgc");
+        self.link_arg("-bgc");
     }
 
     fn no_gc_sections(&mut self) {
-        self.cmd.arg("-bnogc");
+        self.link_arg("-bnogc");
     }
 
     fn optimize(&mut self) {}
 
     fn pgo_gen(&mut self) {
-        self.cmd.arg("-bdbg:namedsects:ss");
+        self.link_arg("-bdbg:namedsects:ss");
     }
 
     fn control_flow_guard(&mut self) {}
@@ -1618,7 +1601,7 @@ impl<'a> Linker for AixLinker<'a> {
         if let Err(e) = res {
             self.sess.dcx().fatal(format!("failed to write export file: {e}"));
         }
-        self.cmd.arg(format!("-bE:{}", path.to_str().unwrap()));
+        self.link_arg(format!("-bE:{}", path.to_str().unwrap()));
     }
 
     fn subsystem(&mut self, _subsystem: &str) {}
@@ -1747,39 +1730,27 @@ impl<'a> Linker for PtxLinker<'a> {
     }
 
     fn link_staticlib_by_path(&mut self, path: &Path, _whole_archive: bool) {
-        self.cmd.arg("--rlib").arg(path);
-    }
-
-    fn include_path(&mut self, path: &Path) {
-        self.cmd.arg("-L").arg(path);
+        self.link_arg("--rlib").link_arg(path);
     }
 
     fn debuginfo(&mut self, _strip: Strip, _: &[PathBuf]) {
-        self.cmd.arg("--debug");
+        self.link_arg("--debug");
     }
 
     fn add_object(&mut self, path: &Path) {
-        self.cmd.arg("--bitcode").arg(path);
+        self.link_arg("--bitcode").link_arg(path);
     }
 
     fn optimize(&mut self) {
         match self.sess.lto() {
             Lto::Thin | Lto::Fat | Lto::ThinLocal => {
-                self.cmd.arg("-Olto");
+                self.link_arg("-Olto");
             }
 
             Lto::No => {}
         }
     }
 
-    fn output_filename(&mut self, path: &Path) {
-        self.cmd.arg("-o").arg(path);
-    }
-
-    fn framework_path(&mut self, _path: &Path) {
-        panic!("frameworks not supported")
-    }
-
     fn full_relro(&mut self) {}
 
     fn partial_relro(&mut self) {}
@@ -1829,19 +1800,11 @@ impl<'a> Linker for LlbcLinker<'a> {
     }
 
     fn link_staticlib_by_path(&mut self, path: &Path, _whole_archive: bool) {
-        self.cmd.arg(path);
-    }
-
-    fn include_path(&mut self, path: &Path) {
-        self.cmd.arg("-L").arg(path);
+        self.link_or_cc_arg(path);
     }
 
     fn debuginfo(&mut self, _strip: Strip, _: &[PathBuf]) {
-        self.cmd.arg("--debug");
-    }
-
-    fn add_object(&mut self, path: &Path) {
-        self.cmd.arg(path);
+        self.link_arg("--debug");
     }
 
     fn optimize(&mut self) {
@@ -1855,14 +1818,6 @@ impl<'a> Linker for LlbcLinker<'a> {
         };
     }
 
-    fn output_filename(&mut self, path: &Path) {
-        self.cmd.arg("-o").arg(path);
-    }
-
-    fn framework_path(&mut self, _path: &Path) {
-        panic!("frameworks not supported")
-    }
-
     fn full_relro(&mut self) {}
 
     fn partial_relro(&mut self) {}
@@ -1887,7 +1842,7 @@ impl<'a> Linker for LlbcLinker<'a> {
         match _crate_type {
             CrateType::Cdylib => {
                 for sym in symbols {
-                    self.cmd.arg("--export-symbol").arg(sym);
+                    self.link_args(&["--export-symbol", sym]);
                 }
             }
             _ => (),
@@ -1920,23 +1875,15 @@ impl<'a> Linker for BpfLinker<'a> {
     }
 
     fn link_staticlib_by_path(&mut self, path: &Path, _whole_archive: bool) {
-        self.cmd.arg(path);
-    }
-
-    fn include_path(&mut self, path: &Path) {
-        self.cmd.arg("-L").arg(path);
+        self.link_or_cc_arg(path);
     }
 
     fn debuginfo(&mut self, _strip: Strip, _: &[PathBuf]) {
-        self.cmd.arg("--debug");
-    }
-
-    fn add_object(&mut self, path: &Path) {
-        self.cmd.arg(path);
+        self.link_arg("--debug");
     }
 
     fn optimize(&mut self) {
-        self.cmd.arg(match self.sess.opts.optimize {
+        self.link_arg(match self.sess.opts.optimize {
             OptLevel::No => "-O0",
             OptLevel::Less => "-O1",
             OptLevel::Default => "-O2",
@@ -1946,14 +1893,6 @@ impl<'a> Linker for BpfLinker<'a> {
         });
     }
 
-    fn output_filename(&mut self, path: &Path) {
-        self.cmd.arg("-o").arg(path);
-    }
-
-    fn framework_path(&mut self, _path: &Path) {
-        panic!("frameworks not supported")
-    }
-
     fn full_relro(&mut self) {}
 
     fn partial_relro(&mut self) {}
@@ -1985,7 +1924,7 @@ impl<'a> Linker for BpfLinker<'a> {
         if let Err(error) = res {
             self.sess.dcx().emit_fatal(errors::SymbolFileWriteFailure { error });
         } else {
-            self.cmd.arg("--export-symbols").arg(&path);
+            self.link_arg("--export-symbols").link_arg(&path);
         }
     }
 
diff --git a/compiler/rustc_codegen_ssa/src/back/rpath.rs b/compiler/rustc_codegen_ssa/src/back/rpath.rs
index f499bbcf853..82070d4453b 100644
--- a/compiler/rustc_codegen_ssa/src/back/rpath.rs
+++ b/compiler/rustc_codegen_ssa/src/back/rpath.rs
@@ -9,16 +9,10 @@ pub struct RPathConfig<'a> {
     pub libs: &'a [&'a Path],
     pub out_filename: PathBuf,
     pub is_like_osx: bool,
-    pub has_rpath: bool,
     pub linker_is_gnu: bool,
 }
 
 pub fn get_rpath_flags(config: &RPathConfig<'_>) -> Vec<OsString> {
-    // No rpath on windows
-    if !config.has_rpath {
-        return Vec::new();
-    }
-
     debug!("preparing the RPATH!");
 
     let rpaths = get_rpaths(config);
diff --git a/compiler/rustc_codegen_ssa/src/back/rpath/tests.rs b/compiler/rustc_codegen_ssa/src/back/rpath/tests.rs
index 0de90a1036e..c620e92db1f 100644
--- a/compiler/rustc_codegen_ssa/src/back/rpath/tests.rs
+++ b/compiler/rustc_codegen_ssa/src/back/rpath/tests.rs
@@ -37,7 +37,6 @@ fn test_rpath_relative() {
     if cfg!(target_os = "macos") {
         let config = &mut RPathConfig {
             libs: &[],
-            has_rpath: true,
             is_like_osx: true,
             linker_is_gnu: false,
             out_filename: PathBuf::from("bin/rustc"),
@@ -48,7 +47,6 @@ fn test_rpath_relative() {
         let config = &mut RPathConfig {
             libs: &[],
             out_filename: PathBuf::from("bin/rustc"),
-            has_rpath: true,
             is_like_osx: false,
             linker_is_gnu: true,
         };
@@ -62,7 +60,6 @@ fn test_rpath_relative_issue_119571() {
     let config = &mut RPathConfig {
         libs: &[],
         out_filename: PathBuf::from("rustc"),
-        has_rpath: true,
         is_like_osx: false,
         linker_is_gnu: true,
     };