Skip to content

Conversation

CendioOssman
Copy link

Various fixes to make sure clang can find libstdc++ headers and library for current versions of gcc.

Tested with a cross-compiler setup, but should hopefully work correctly for a native setup as well.

Also adds support for statically linking libstdc++ and libgcc_s.

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 Aug 25, 2025
@llvmbot
Copy link
Member

llvmbot commented Aug 25, 2025

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-driver

Author: Pierre Ossman (ThinLinc team) (CendioOssman)

Changes

Various fixes to make sure clang can find libstdc++ headers and library for current versions of gcc.

Tested with a cross-compiler setup, but should hopefully work correctly for a native setup as well.

Also adds support for statically linking libstdc++ and libgcc_s.


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

4 Files Affected:

  • (modified) clang/lib/Driver/ToolChains/Darwin.cpp (+116-31)
  • (modified) clang/lib/Driver/ToolChains/Darwin.h (+17)
  • (modified) clang/lib/Driver/ToolChains/Gnu.cpp (+11-6)
  • (modified) clang/test/Driver/pic.c (+2-2)
diff --git a/clang/lib/Driver/ToolChains/Darwin.cpp b/clang/lib/Driver/ToolChains/Darwin.cpp
index 234683f2f4882..f71b875d08c11 100644
--- a/clang/lib/Driver/ToolChains/Darwin.cpp
+++ b/clang/lib/Driver/ToolChains/Darwin.cpp
@@ -681,6 +681,8 @@ void darwin::Linker::ConstructJob(Compilation &C, const JobAction &JA,
 
   Args.AddAllArgs(CmdArgs, options::OPT_L);
 
+  getToolChain().AddFilePathLibArgs(Args, CmdArgs);
+
   AddLinkerInputs(getToolChain(), Inputs, Args, CmdArgs, JA);
   // Build the input file for -filelist (list of linker input files) in case we
   // need it later
@@ -1177,7 +1179,31 @@ Tool *MachO::buildAssembler() const {
 
 DarwinClang::DarwinClang(const Driver &D, const llvm::Triple &Triple,
                          const ArgList &Args)
-    : Darwin(D, Triple, Args) {}
+    : Darwin(D, Triple, Args), GCCInstallation(D) {
+  GCCInstallation.init(Triple, Args);
+  if (GCCInstallation.isValid()) {
+    StringRef LibDir = GCCInstallation.getParentLibPath();
+    StringRef TripleStr = GCCInstallation.getTriple().str();
+    const Generic_GCC::GCCVersion &Version = GCCInstallation.getVersion();
+
+    std::string Path;
+
+    // Try /gcc/$triple/$version/
+    Path = LibDir.str() + "/gcc/" + TripleStr.str() + "/" + Version.Text;
+    if (getVFS().exists(Path))
+      getFilePaths().push_back(Path);
+
+    // Try /gcc/$triple/lib/
+    Path = LibDir.str() + "/gcc/" + TripleStr.str() + "/lib";
+    if (getVFS().exists(Path))
+      getFilePaths().push_back(Path);
+
+    // Try /../$triple/lib/
+    Path = LibDir.str() + "/../" + TripleStr.str() + "/lib";
+    if (getVFS().exists(Path))
+      getFilePaths().push_back(Path);
+  }
+}
 
 void DarwinClang::addClangWarningOptions(ArgStringList &CC1Args) const {
   // Always error about undefined 'TARGET_OS_*' macros.
@@ -1547,14 +1573,6 @@ void DarwinClang::AddLinkRuntimeLibArgs(const ArgList &Args,
     return;
   }
 
-  // Reject -static-libgcc for now, we can deal with this when and if someone
-  // cares. This is useful in situations where someone wants to statically link
-  // something like libstdc++, and needs its runtime support routines.
-  if (const Arg *A = Args.getLastArg(options::OPT_static_libgcc)) {
-    getDriver().Diag(diag::err_drv_unsupported_opt) << A->getAsString(Args);
-    return;
-  }
-
   const SanitizerArgs &Sanitize = getSanitizerArgs(Args);
 
   if (!Sanitize.needsSharedRt()) {
@@ -2793,9 +2811,52 @@ void AppleMachO::AddGnuCPlusPlusIncludePaths(
     const llvm::opt::ArgList &DriverArgs,
     llvm::opt::ArgStringList &CC1Args) const {}
 
+bool DarwinClang::addLibStdCXXIncludePaths(Twine IncludeDir, StringRef Triple,
+                                           const llvm::opt::ArgList &DriverArgs,
+                                           llvm::opt::ArgStringList &CC1Args) const {
+  if (!getVFS().exists(IncludeDir))
+    return false;
+
+  // GPLUSPLUS_INCLUDE_DIR
+  addSystemInclude(DriverArgs, CC1Args, IncludeDir);
+  // GPLUSPLUS_TOOL_INCLUDE_DIR
+  addSystemInclude(DriverArgs, CC1Args, IncludeDir + "/" + Triple);
+  // GPLUSPLUS_BACKWARD_INCLUDE_DIR
+  addSystemInclude(DriverArgs, CC1Args, IncludeDir + "/backward");
+
+  return true;
+}
+
 void DarwinClang::AddGnuCPlusPlusIncludePaths(
     const llvm::opt::ArgList &DriverArgs,
     llvm::opt::ArgStringList &CC1Args) const {
+  if (GCCInstallation.isValid()) {
+    // This is a stripped down version of Generic_GCC::addGCCLibStdCxxIncludePaths.
+    StringRef LibDir = GCCInstallation.getParentLibPath();
+    StringRef TripleStr = GCCInstallation.getTriple().str();
+    const Generic_GCC::GCCVersion &Version = GCCInstallation.getVersion();
+
+    // Try /../$triple/include/c++/$version
+    if (addLibStdCXXIncludePaths(
+            LibDir.str() + "/../" + TripleStr + "/include/c++/" + Version.Text,
+            TripleStr, DriverArgs, CC1Args))
+      return;
+
+    // Try /gcc/$triple/$version/include/c++/
+    if (addLibStdCXXIncludePaths(LibDir.str() + "/gcc/" + TripleStr + "/" +
+                                    Version.Text + "/include/c++/",
+                                TripleStr, DriverArgs, CC1Args))
+      return;
+
+    // Try /../include/c++/$version
+    if (addLibStdCXXIncludePaths(LibDir.str() + "/../include/c++/" + Version.Text,
+                                TripleStr, DriverArgs, CC1Args))
+      return;
+
+    getDriver().Diag(diag::warn_drv_libstdcxx_not_found);
+    return;
+  }
+
   llvm::SmallString<128> UsrIncludeCxx = GetEffectiveSysroot(DriverArgs);
   llvm::sys::path::append(UsrIncludeCxx, "usr", "include", "c++");
 
@@ -2848,39 +2909,57 @@ void AppleMachO::AddCXXStdlibLibArgs(const ArgList &Args,
     break;
 
   case ToolChain::CST_Libstdcxx:
-    // Unfortunately, -lstdc++ doesn't always exist in the standard search path;
-    // it was previously found in the gcc lib dir. However, for all the Darwin
-    // platforms we care about it was -lstdc++.6, so we search for that
-    // explicitly if we can't see an obvious -lstdc++ candidate.
+    AddGnuCPlusPlusStdlibLibArgs(Args, CmdArgs);
+    break;
+  }
+}
 
-    // Check in the sysroot first.
-    if (const Arg *A = Args.getLastArg(options::OPT_isysroot)) {
-      SmallString<128> P(A->getValue());
-      llvm::sys::path::append(P, "usr", "lib", "libstdc++.dylib");
+void AppleMachO::AddGnuCPlusPlusStdlibLibArgs(
+    const ArgList &Args, ArgStringList &CmdArgs) const {
+  CmdArgs.push_back("-lstdc++");
+}
 
-      if (!getVFS().exists(P)) {
-        llvm::sys::path::remove_filename(P);
-        llvm::sys::path::append(P, "libstdc++.6.dylib");
-        if (getVFS().exists(P)) {
-          CmdArgs.push_back(Args.MakeArgString(P));
+void DarwinClang::AddGnuCPlusPlusStdlibLibArgs(
+    const ArgList &Args, ArgStringList &CmdArgs) const {
+  if (GCCInstallation.isValid()) {
+    if (Args.hasArg(options::OPT_static_libstdcxx)) {
+      // ld64 doesn't support -Bstatic, so we need to find the actual library
+      for (const auto &Path : getFilePaths()) {
+        llvm::SmallString<128> UsrLibStdCxx(Path);
+        llvm::sys::path::append(UsrLibStdCxx, "libstdc++.a");
+        if (getVFS().exists(UsrLibStdCxx)) {
+          CmdArgs.push_back(Args.MakeArgString(UsrLibStdCxx));
+          // libstdcxx++ needs symbols from here
+          if (Args.hasArg(options::OPT_static_libgcc))
+            CmdArgs.push_back("-lgcc_eh");
+          else
+            CmdArgs.push_back("-lgcc_s.1");
           return;
         }
       }
     }
+  } else {
+    // Unfortunately, -lstdc++ doesn't always exist in the standard search path;
+    // it was previously found in the gcc lib dir. However, for all the Darwin
+    // platforms we care about it was -lstdc++.6, so we search for that
+    // explicitly if we can't see an obvious -lstdc++ candidate.
+
+    llvm::SmallString<128> UsrLibStdCxx = GetEffectiveSysroot(Args);
+    llvm::sys::path::append(UsrLibStdCxx, "usr", "lib", "libstdc++.dylib");
 
-    // Otherwise, look in the root.
     // FIXME: This should be removed someday when we don't have to care about
     // 10.6 and earlier, where /usr/lib/libstdc++.dylib does not exist.
-    if (!getVFS().exists("/usr/lib/libstdc++.dylib") &&
-        getVFS().exists("/usr/lib/libstdc++.6.dylib")) {
-      CmdArgs.push_back("/usr/lib/libstdc++.6.dylib");
-      return;
+    if (!getVFS().exists(UsrLibStdCxx)) {
+      llvm::sys::path::remove_filename(UsrLibStdCxx);
+      llvm::sys::path::append(UsrLibStdCxx, "libstdc++.6.dylib");
+      if (getVFS().exists(UsrLibStdCxx)) {
+        CmdArgs.push_back(Args.MakeArgString(UsrLibStdCxx));
+        return;
+      }
     }
-
-    // Otherwise, let the linker search.
-    CmdArgs.push_back("-lstdc++");
-    break;
   }
+
+  CmdArgs.push_back("-lstdc++");
 }
 
 void DarwinClang::AddCCKextLibArgs(const ArgList &Args,
@@ -3785,3 +3864,9 @@ void AppleMachO::printVerboseInfo(raw_ostream &OS) const {
   CudaInstallation->print(OS);
   RocmInstallation->print(OS);
 }
+
+void DarwinClang::printVerboseInfo(raw_ostream &OS) const {
+  // Print the information about how we detected the GCC installation.
+  GCCInstallation.print(OS);
+  Darwin::printVerboseInfo(OS);
+}
diff --git a/clang/lib/Driver/ToolChains/Darwin.h b/clang/lib/Driver/ToolChains/Darwin.h
index d1cfb6f4a5bf7..06990df8f7bbe 100644
--- a/clang/lib/Driver/ToolChains/Darwin.h
+++ b/clang/lib/Driver/ToolChains/Darwin.h
@@ -9,6 +9,7 @@
 #ifndef LLVM_CLANG_LIB_DRIVER_TOOLCHAINS_DARWIN_H
 #define LLVM_CLANG_LIB_DRIVER_TOOLCHAINS_DARWIN_H
 
+#include "Gnu.h"
 #include "clang/Basic/DarwinSDKInfo.h"
 #include "clang/Basic/LangOptions.h"
 #include "clang/Driver/CudaInstallationDetector.h"
@@ -338,6 +339,9 @@ class LLVM_LIBRARY_VISIBILITY AppleMachO : public MachO {
   virtual void
   AddGnuCPlusPlusIncludePaths(const llvm::opt::ArgList &DriverArgs,
                               llvm::opt::ArgStringList &CC1Args) const;
+  virtual void
+  AddGnuCPlusPlusStdlibLibArgs(const llvm::opt::ArgList &Args,
+                               llvm::opt::ArgStringList &CmdArgs) const;
 };
 
 /// Darwin - The base Darwin tool chain.
@@ -640,6 +644,9 @@ class LLVM_LIBRARY_VISIBILITY Darwin : public AppleMachO {
 
 /// DarwinClang - The Darwin toolchain used by Clang.
 class LLVM_LIBRARY_VISIBILITY DarwinClang : public Darwin {
+protected:
+  Generic_GCC::GCCInstallationDetector GCCInstallation;
+
 public:
   DarwinClang(const Driver &D, const llvm::Triple &Triple,
               const llvm::opt::ArgList &Args);
@@ -670,6 +677,8 @@ class LLVM_LIBRARY_VISIBILITY DarwinClang : public Darwin {
   void AddLinkARCArgs(const llvm::opt::ArgList &Args,
                       llvm::opt::ArgStringList &CmdArgs) const override;
 
+  void printVerboseInfo(raw_ostream &OS) const override;
+
   unsigned GetDefaultDwarfVersion() const override;
   // Until dtrace (via CTF) and LLDB can deal with distributed debug info,
   // Darwin defaults to standalone/full debug info.
@@ -686,9 +695,17 @@ class LLVM_LIBRARY_VISIBILITY DarwinClang : public Darwin {
                                StringRef Sanitizer,
                                bool shared = true) const;
 
+  bool
+  addLibStdCXXIncludePaths(Twine IncludeDir, StringRef Triple,
+                           const llvm::opt::ArgList &DriverArgs,
+                           llvm::opt::ArgStringList &CC1Args) const;
+
   void
   AddGnuCPlusPlusIncludePaths(const llvm::opt::ArgList &DriverArgs,
                               llvm::opt::ArgStringList &CC1Args) const override;
+  void
+  AddGnuCPlusPlusStdlibLibArgs(const llvm::opt::ArgList &Args,
+                               llvm::opt::ArgStringList &CmdArgs) const override;
 
   bool AddGnuCPlusPlusIncludePaths(const llvm::opt::ArgList &DriverArgs,
                                    llvm::opt::ArgStringList &CC1Args,
diff --git a/clang/lib/Driver/ToolChains/Gnu.cpp b/clang/lib/Driver/ToolChains/Gnu.cpp
index 01b146db24f3e..1d17fb6753bd9 100644
--- a/clang/lib/Driver/ToolChains/Gnu.cpp
+++ b/clang/lib/Driver/ToolChains/Gnu.cpp
@@ -1864,9 +1864,11 @@ static bool findBiarchMultilibs(const Driver &D,
                             .flag("-m64", /*Disallow=*/true)
                             .makeMultilib();
 
-  // GCC toolchain for IAMCU doesn't have crtbegin.o, so look for libgcc.a.
+  // GCC toolchain for IAMCU and Darwin doesn't have crtbegin.o, so look for libgcc.a.
   FilterNonExistent NonExistent(
-      Path, TargetTriple.isOSIAMCU() ? "/libgcc.a" : "/crtbegin.o", D.getVFS());
+      Path,
+      (TargetTriple.isOSIAMCU() || TargetTriple.isOSDarwin()) ? "/libgcc.a" : "/crtbegin.o",
+      D.getVFS());
 
   // Determine default multilib from: 32, 64, x32
   // Also handle cases such as 64 on 32, 32 on 64, etc.
@@ -2161,10 +2163,8 @@ void Generic_GCC::GCCInstallationDetector::init(
 
     // Next, look for prefix(es) that correspond to distribution-supplied gcc
     // installations.
-    if (D.SysRoot.empty()) {
-      // Typically /usr.
-      AddDefaultGCCPrefixes(TargetTriple, Prefixes, D.SysRoot);
-    }
+    // Typically /usr.
+    AddDefaultGCCPrefixes(TargetTriple, Prefixes, "");
 
     // Try to respect gcc-config on Gentoo if --gcc-toolchain is not provided.
     // This avoids accidentally enforcing the system GCC version when using a
@@ -2556,6 +2556,11 @@ void Generic_GCC::GCCInstallationDetector::AddDefaultGCCPrefixes(
     return;
   }
 
+  if (TargetTriple.isOSDarwin()) {
+    LibDirs.push_back("/lib");
+    return;
+  }
+
   switch (TargetTriple.getArch()) {
   case llvm::Triple::aarch64:
     LibDirs.append(begin(AArch64LibDirs), end(AArch64LibDirs));
diff --git a/clang/test/Driver/pic.c b/clang/test/Driver/pic.c
index f5d0745422790..24bc26937dda3 100644
--- a/clang/test/Driver/pic.c
+++ b/clang/test/Driver/pic.c
@@ -26,8 +26,8 @@
 //
 // CHECK-PIE-LD: "{{.*}}ld{{(.exe)?}}"
 // CHECK-PIE-LD: "-pie"
-// CHECK-PIE-LD: "Scrt1.o" "crti.o" "crtbeginS.o"
-// CHECK-PIE-LD: "crtendS.o" "crtn.o"
+// CHECK-PIE-LD: "Scrt1.o" "crti.o" "{{.*}}crtbeginS.o"
+// CHECK-PIE-LD: "{{.*}}crtendS.o" "crtn.o"
 //
 // CHECK-NOPIE-LD: "-nopie"
 //

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