Skip to content

Conversation

daniel-levin
Copy link

@daniel-levin daniel-levin commented Oct 11, 2025

Overview

This change fixes and refactors the code that decides which linker to use on Solaris (and derivatives like Illumos). It turns out that the --ld-path option has never worked on Solaris. This commit also fixes three FIXMEs along the way.

The primary logical change in this commit is the inversion of checking if we're using GNU ld to checking if we're using Solaris's build-in linker.

Benefits

Users can now use third-party GNU-ld compatible linkers such as Mold and Wild.

Testing

In addition to running the llvm-lit tests affected by this change (.e.g /test/Driver/solaris-ld), I manually tested every branch that makes a determination of which linker to use.

Copy link

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' labels Oct 11, 2025
@llvmbot
Copy link
Member

llvmbot commented Oct 11, 2025

@llvm/pr-subscribers-clang-driver

@llvm/pr-subscribers-clang

Author: Daniel Levin (daniel-levin)

Changes

Overview

This change fixes and refactors the code that decides which linker to use on Solaris (and derivatives like Illumos). It turns out that the --ld-path option has never worked on Solaris. This commit also fixes three FIXMEs along the way.

Testing

In addition to running the llvm-lit tests affected by this change (.e.g /test/Driver/solaris-ld), I manually tested every branch that makes a determination of which linker to use.


Full diff: https://github.com/llvm/llvm-project/pull/163000.diff

4 Files Affected:

  • (modified) clang/include/clang/Driver/ToolChain.h (+2)
  • (modified) clang/lib/Driver/ToolChains/CommonArgs.cpp (+4-4)
  • (modified) clang/lib/Driver/ToolChains/Solaris.cpp (+83-36)
  • (modified) clang/lib/Driver/ToolChains/Solaris.h (+30-1)
diff --git a/clang/include/clang/Driver/ToolChain.h b/clang/include/clang/Driver/ToolChain.h
index 1425714d34110..753742067fe8c 100644
--- a/clang/include/clang/Driver/ToolChain.h
+++ b/clang/include/clang/Driver/ToolChain.h
@@ -491,6 +491,8 @@ class ToolChain {
   }
 
   /// GetDefaultLinker - Get the default linker to use.
+  /// Note: this is distinct from the 'preferred' linker, which is optionally
+  /// set at compile time using CLANG_DEFAULT_LINKER.
   virtual const char *getDefaultLinker() const { return "ld"; }
 
   /// GetDefaultRuntimeLibType - Get the default runtime library variant to use.
diff --git a/clang/lib/Driver/ToolChains/CommonArgs.cpp b/clang/lib/Driver/ToolChains/CommonArgs.cpp
index 16cc1db0a2235..87a6f9a89b904 100644
--- a/clang/lib/Driver/ToolChains/CommonArgs.cpp
+++ b/clang/lib/Driver/ToolChains/CommonArgs.cpp
@@ -1480,11 +1480,11 @@ static void addSanitizerRuntime(const ToolChain &TC, const ArgList &Args,
 static bool addSanitizerDynamicList(const ToolChain &TC, const ArgList &Args,
                                     ArgStringList &CmdArgs,
                                     StringRef Sanitizer) {
-  bool LinkerIsGnuLd = solaris::isLinkerGnuLd(TC, Args);
+  bool LinkerIsSolarisLinkEditor = solaris::isLinkerSolarisLinkEditor(TC, Args);
 
   // Solaris ld defaults to --export-dynamic behaviour but doesn't support
   // the option, so don't try to pass it.
-  if (TC.getTriple().isOSSolaris() && !LinkerIsGnuLd)
+  if (TC.getTriple().isOSSolaris() && LinkerIsSolarisLinkEditor)
     return true;
   SmallString<128> SanRT(TC.getCompilerRT(Args, Sanitizer));
   if (llvm::sys::fs::exists(SanRT + ".syms")) {
@@ -1500,14 +1500,14 @@ void tools::addAsNeededOption(const ToolChain &TC,
                               bool as_needed) {
   assert(!TC.getTriple().isOSAIX() &&
          "AIX linker does not support any form of --as-needed option yet.");
-  bool LinkerIsGnuLd = solaris::isLinkerGnuLd(TC, Args);
+  bool LinkerIsSolarisLinkEditor = solaris::isLinkerSolarisLinkEditor(TC, Args);
 
   // While the Solaris 11.2 ld added --as-needed/--no-as-needed as aliases
   // for the native forms -z ignore/-z record, they are missing in Illumos,
   // so always use the native form.
   // GNU ld doesn't support -z ignore/-z record, so don't use them even on
   // Solaris.
-  if (TC.getTriple().isOSSolaris() && !LinkerIsGnuLd) {
+  if (TC.getTriple().isOSSolaris() && LinkerIsSolarisLinkEditor) {
     CmdArgs.push_back("-z");
     CmdArgs.push_back(as_needed ? "ignore" : "record");
   } else {
diff --git a/clang/lib/Driver/ToolChains/Solaris.cpp b/clang/lib/Driver/ToolChains/Solaris.cpp
index 02aa59817449d..edd794d19cbe5 100644
--- a/clang/lib/Driver/ToolChains/Solaris.cpp
+++ b/clang/lib/Driver/ToolChains/Solaris.cpp
@@ -36,11 +36,12 @@ void solaris::Assembler::ConstructJob(Compilation &C, const JobAction &JA,
   gnutools::Assembler::ConstructJob(C, JA, Output, Inputs, Args, LinkingOutput);
 }
 
-bool solaris::isLinkerGnuLd(const ToolChain &TC, const ArgList &Args) {
-  // Only used if targetting Solaris.
-  const Arg *A = Args.getLastArg(options::OPT_fuse_ld_EQ);
-  StringRef UseLinker = A ? A->getValue() : TC.getDriver().getPreferredLinker();
-  return UseLinker == "bfd" || UseLinker == "gld";
+bool solaris::isLinkerSolarisLinkEditor(const ToolChain &TC,
+                                        const ArgList &Args) {
+  auto Determination =
+      solaris::LinkerDetermination::make(TC, Args, /* EmitDiagnostics */ false);
+  return Determination.Expectations ==
+         solaris::LinkerExpectations::SolarisLinkEditor;
 }
 
 static bool getPIE(const ArgList &Args, const ToolChain &TC) {
@@ -52,30 +53,11 @@ static bool getPIE(const ArgList &Args, const ToolChain &TC) {
                       TC.isPIEDefault(Args));
 }
 
-// FIXME: Need to handle PreferredLinker here?
 std::string solaris::Linker::getLinkerPath(const ArgList &Args) const {
-  const ToolChain &ToolChain = getToolChain();
-  if (const Arg *A = Args.getLastArg(options::OPT_fuse_ld_EQ)) {
-    StringRef UseLinker = A->getValue();
-    if (!UseLinker.empty()) {
-      if (llvm::sys::path::is_absolute(UseLinker) &&
-          llvm::sys::fs::can_execute(UseLinker))
-        return std::string(UseLinker);
-
-      // Accept 'bfd' and 'gld' as aliases for the GNU linker.
-      if (UseLinker == "bfd" || UseLinker == "gld")
-        // FIXME: Could also use /usr/bin/gld here.
-        return "/usr/gnu/bin/ld";
+  auto Determination =
+      solaris::LinkerDetermination::make(getToolChain(), Args, true);
 
-      // Accept 'ld' as alias for the default linker
-      if (UseLinker != "ld")
-        ToolChain.getDriver().Diag(diag::err_drv_invalid_linker_name)
-            << A->getAsString(Args);
-    }
-  }
-
-  // getDefaultLinker() always returns an absolute path.
-  return ToolChain.getDefaultLinker();
+  return Determination.Linker;
 }
 
 void solaris::Linker::ConstructJob(Compilation &C, const JobAction &JA,
@@ -87,11 +69,11 @@ void solaris::Linker::ConstructJob(Compilation &C, const JobAction &JA,
   const Driver &D = ToolChain.getDriver();
   const llvm::Triple::ArchType Arch = ToolChain.getArch();
   const bool IsPIE = getPIE(Args, ToolChain);
-  const bool LinkerIsGnuLd = isLinkerGnuLd(ToolChain, Args);
+  const bool LinkerIsSolarisLd = isLinkerSolarisLinkEditor(ToolChain, Args);
   ArgStringList CmdArgs;
 
   // Demangle C++ names in errors.  GNU ld already defaults to --demangle.
-  if (!LinkerIsGnuLd)
+  if (LinkerIsSolarisLd)
     CmdArgs.push_back("-C");
 
   if (!Args.hasArg(options::OPT_nostdlib, options::OPT_shared,
@@ -101,7 +83,7 @@ void solaris::Linker::ConstructJob(Compilation &C, const JobAction &JA,
   }
 
   if (IsPIE) {
-    if (LinkerIsGnuLd) {
+    if (!LinkerIsSolarisLd) {
       CmdArgs.push_back("-pie");
     } else {
       CmdArgs.push_back("-z");
@@ -122,7 +104,7 @@ void solaris::Linker::ConstructJob(Compilation &C, const JobAction &JA,
     Args.ClaimAllArgs(options::OPT_pthreads);
   }
 
-  if (LinkerIsGnuLd) {
+  if (!LinkerIsSolarisLd) {
     // Set the correct linker emulation for 32- and 64-bit Solaris.
     switch (Arch) {
     case llvm::Triple::x86:
@@ -256,7 +238,7 @@ void solaris::Linker::ConstructJob(Compilation &C, const JobAction &JA,
       if (Arch == llvm::Triple::x86_64 &&
           (SA.needsAsanRt() || SA.needsStatsRt() ||
            (SA.needsUbsanRt() && !SA.requiresMinimalRuntime())) &&
-          !LinkerIsGnuLd) {
+          LinkerIsSolarisLd) {
         CmdArgs.push_back("-z");
         CmdArgs.push_back("relax=transtls");
       }
@@ -344,10 +326,10 @@ SanitizerMask Solaris::getSupportedSanitizers() const {
 }
 
 const char *Solaris::getDefaultLinker() const {
-  // FIXME: Only handle Solaris ld and GNU ld here.
-  return llvm::StringSwitch<const char *>(getDriver().getPreferredLinker())
-      .Cases("bfd", "gld", "/usr/gnu/bin/ld")
-      .Default("/usr/bin/ld");
+  // The default linker on Solaris is _always_ the Solaris Link Editor.
+  // Recall that the driver's _default_ linker is distinct from the compile-time
+  // _preferred_ linker setting CLANG_DEFAULT_LINKER, which may even be empty.
+  return "/usr/bin/ld";
 }
 
 Tool *Solaris::buildAssembler() const {
@@ -423,3 +405,68 @@ void Solaris::addLibStdCxxIncludePaths(
                            TripleStr, Multilib.includeSuffix(), DriverArgs,
                            CC1Args);
 }
+
+solaris::LinkerDetermination
+solaris::LinkerDetermination::make(const ToolChain &TC, const ArgList &Args,
+                                   bool EmitDiagnostics) {
+  // First, check --ld-path, then -fuse-ld, then the compile-time
+  // preferred linker (CLANG_DEFAULT_LINKER), then finally fall back to the
+  // platform's default - the Solaris Link Editor. This behavior is consonant
+  // with the other platforms' drivers.
+
+  auto InferExpectations =
+      [](const std::string &s) -> solaris::LinkerExpectations {
+    if (s == "ld" || s == "/bin/ld" || s == "/usr/bin/ld")
+      return solaris::LinkerExpectations::SolarisLinkEditor;
+    return solaris::LinkerExpectations::GnuLdCompatibleArgParser;
+  };
+
+  if (const Arg *A = Args.getLastArg(options::OPT_ld_path_EQ)) {
+    StringRef UseLinker = A->getValue();
+    if (!UseLinker.empty()) {
+      auto LinkerPath = std::string(UseLinker);
+      if (llvm::sys::fs::can_execute(LinkerPath))
+        return solaris::LinkerDetermination(LinkerPath,
+                                            InferExpectations(LinkerPath));
+    }
+    if (EmitDiagnostics)
+      TC.getDriver().Diag(diag::err_drv_invalid_linker_name)
+          << A->getAsString(Args);
+  } else if (const Arg *A = Args.getLastArg(options::OPT_fuse_ld_EQ)) {
+    StringRef UseLinker = A->getValue();
+    if (!UseLinker.empty()) {
+      if (llvm::sys::path::is_absolute(UseLinker) &&
+          llvm::sys::fs::can_execute(UseLinker)) {
+        auto LinkerPath = std::string(UseLinker);
+        return solaris::LinkerDetermination(LinkerPath,
+                                            InferExpectations(LinkerPath));
+      }
+
+      // Accept 'bfd' and 'gld' as aliases for the GNU linker.
+      if (UseLinker == "bfd" || UseLinker == "gld")
+        return solaris::LinkerDetermination(
+            "/usr/gnu/bin/ld",
+            solaris::LinkerExpectations::GnuLdCompatibleArgParser);
+
+      // Accept 'ld' as an alias for the default linker
+      if (UseLinker == "ld")
+        return solaris::LinkerDetermination(
+            "/usr/bin/ld", solaris::LinkerExpectations::SolarisLinkEditor);
+
+      if (EmitDiagnostics)
+        TC.getDriver().Diag(diag::err_drv_invalid_linker_name)
+            << A->getAsString(Args);
+    }
+  }
+
+  auto CompileTimePreferredLinker = TC.getDriver().getPreferredLinker();
+  if (!CompileTimePreferredLinker.empty()) {
+    auto LinkerPath = std::string(CompileTimePreferredLinker);
+    return solaris::LinkerDetermination(LinkerPath,
+                                        InferExpectations(LinkerPath));
+  }
+
+  return solaris::LinkerDetermination(
+      std::string("/usr/bin/ld"),
+      solaris::LinkerExpectations::SolarisLinkEditor);
+}
diff --git a/clang/lib/Driver/ToolChains/Solaris.h b/clang/lib/Driver/ToolChains/Solaris.h
index 9ec83b773da44..0d3a78ff09189 100644
--- a/clang/lib/Driver/ToolChains/Solaris.h
+++ b/clang/lib/Driver/ToolChains/Solaris.h
@@ -31,7 +31,8 @@ class LLVM_LIBRARY_VISIBILITY Assembler final : public gnutools::Assembler {
                     const char *LinkingOutput) const override;
 };
 
-bool isLinkerGnuLd(const ToolChain &TC, const llvm::opt::ArgList &Args);
+bool isLinkerSolarisLinkEditor(const ToolChain &TC,
+                               const llvm::opt::ArgList &Args);
 
 class LLVM_LIBRARY_VISIBILITY Linker final : public Tool {
 public:
@@ -46,6 +47,34 @@ class LLVM_LIBRARY_VISIBILITY Linker final : public Tool {
                     const llvm::opt::ArgList &TCArgs,
                     const char *LinkingOutput) const override;
 };
+
+enum class LinkerExpectations {
+  GnuLdCompatibleArgParser,
+  /// The formal name for the built-in ld on Solaris.
+  SolarisLinkEditor,
+};
+
+/// We use Solaris's built-in linker by default. It has a unique command line
+/// syntax and specific limitations. By contrast, other linkers such as lld,
+/// Mold, and Wild are compatible with GNU ld's command line syntax. Knowing
+/// _which_ linker to use is sufficient to determine the expectations of that
+/// linker. Rather than spread ad-hoc string comparisons all over the driver, we
+/// encapsulate the details of differences in the chosen linker here.
+class LinkerDetermination final {
+  LinkerDetermination(std::string Linker, LinkerExpectations Expectations)
+      : Linker(Linker), Expectations(Expectations) {}
+
+public:
+  std::string Linker;
+  LinkerExpectations Expectations;
+
+  /// Choose the correct linker based on arguments and compile-time options
+  /// recorded in the ToolChain.
+  static LinkerDetermination make(const ToolChain &TC,
+                                  const llvm::opt::ArgList &Args,
+                                  bool EmitDiagnostics);
+};
+
 } // end namespace solaris
 } // end namespace tools
 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants