Skip to content

[Clang][Driver] Revise Cygwin ToolChain to call linker directly #147960

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

tyan0
Copy link

@tyan0 tyan0 commented Jul 10, 2025

...so that libc++, compiler-rt, and libunwind can be used by the options: -stdlib=libc++, -rtlib=compiler-rt, and -unwindlib=libunwind respectively. Along with this change, the test for this driver is also trimmed a bit.

This is a followup patch for 52924a2.

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 Jul 10, 2025
@llvmbot
Copy link
Member

llvmbot commented Jul 10, 2025

@llvm/pr-subscribers-clang

Author: None (tyan0)

Changes

...so that libc++, compiler-rt, and libunwind can be used by the options: -stdlib=libc++, -rtlib=compiler-rt, and -unwindlib=libunwind respectively. Along with this change, the test for this driver is also trimmed a bit.


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

9 Files Affected:

  • (modified) clang/lib/Driver/ToolChain.cpp (+3)
  • (modified) clang/lib/Driver/ToolChains/Cygwin.cpp (+289)
  • (modified) clang/lib/Driver/ToolChains/Cygwin.h (+21)
  • (added) clang/test/Driver/Inputs/basic_cross_cygwin_tree/usr/lib/gcc/i686-pc-msys/10/crtend.o ()
  • (added) clang/test/Driver/Inputs/basic_cross_cygwin_tree/usr/lib/gcc/x86_64-pc-cygwin/10/crtend.o ()
  • (added) clang/test/Driver/Inputs/basic_cygwin_tree/usr/lib/crt0.o ()
  • (added) clang/test/Driver/Inputs/basic_cygwin_tree/usr/lib/gcc/i686-pc-cygwin/10/crtend.o ()
  • (added) clang/test/Driver/Inputs/basic_cygwin_tree/usr/lib/gcc/x86_64-pc-msys/10/crtend.o ()
  • (modified) clang/test/Driver/cygwin.cpp (+8-8)
diff --git a/clang/lib/Driver/ToolChain.cpp b/clang/lib/Driver/ToolChain.cpp
index 3f9b808b2722e..3c9148ee51fd0 100644
--- a/clang/lib/Driver/ToolChain.cpp
+++ b/clang/lib/Driver/ToolChain.cpp
@@ -684,6 +684,8 @@ static StringRef getArchNameForCompilerRTLib(const ToolChain &TC,
 StringRef ToolChain::getOSLibName() const {
   if (Triple.isOSDarwin())
     return "darwin";
+  if (Triple.isWindowsCygwinEnvironment())
+    return "cygwin";
 
   switch (Triple.getOS()) {
   case llvm::Triple::FreeBSD:
@@ -1504,6 +1506,7 @@ void ToolChain::AddCXXStdlibLibArgs(const ArgList &Args,
   switch (Type) {
   case ToolChain::CST_Libcxx:
     CmdArgs.push_back("-lc++");
+    CmdArgs.push_back("-lc++abi");
     if (Args.hasArg(options::OPT_fexperimental_library))
       CmdArgs.push_back("-lc++experimental");
     break;
diff --git a/clang/lib/Driver/ToolChains/Cygwin.cpp b/clang/lib/Driver/ToolChains/Cygwin.cpp
index d9c16347daa34..82a88065b0c9d 100644
--- a/clang/lib/Driver/ToolChains/Cygwin.cpp
+++ b/clang/lib/Driver/ToolChains/Cygwin.cpp
@@ -9,6 +9,7 @@
 #include "Cygwin.h"
 #include "clang/Config/config.h"
 #include "clang/Driver/CommonArgs.h"
+#include "clang/Driver/Compilation.h"
 #include "clang/Driver/Driver.h"
 #include "clang/Driver/Options.h"
 #include "llvm/Support/Path.h"
@@ -30,6 +31,8 @@ Cygwin::Cygwin(const Driver &D, const llvm::Triple &Triple, const ArgList &Args)
   Generic_GCC::PushPPaths(PPaths);
 
   path_list &Paths = getFilePaths();
+  if (GCCInstallation.isValid())
+    Paths.push_back(GCCInstallation.getInstallPath().str());
 
   Generic_GCC::AddMultiarchPaths(D, SysRoot, "lib", Paths);
 
@@ -107,3 +110,289 @@ void Cygwin::AddClangSystemIncludeArgs(const ArgList &DriverArgs,
   addExternCSystemInclude(DriverArgs, CC1Args, SysRoot + "/usr/include");
   addExternCSystemInclude(DriverArgs, CC1Args, SysRoot + "/usr/include/w32api");
 }
+
+static bool getStaticPIE(const ArgList &Args, const ToolChain &TC) {
+  bool HasStaticPIE = Args.hasArg(options::OPT_static_pie);
+  if (HasStaticPIE && Args.hasArg(options::OPT_no_pie)) {
+    const Driver &D = TC.getDriver();
+    const llvm::opt::OptTable &Opts = D.getOpts();
+    StringRef StaticPIEName = Opts.getOptionName(options::OPT_static_pie);
+    StringRef NoPIEName = Opts.getOptionName(options::OPT_nopie);
+    D.Diag(diag::err_drv_cannot_mix_options) << StaticPIEName << NoPIEName;
+  }
+  return HasStaticPIE;
+}
+
+static bool getStatic(const ArgList &Args) {
+  return Args.hasArg(options::OPT_static) &&
+      !Args.hasArg(options::OPT_static_pie);
+}
+
+void cygwin::Linker::ConstructJob(Compilation &C, const JobAction &JA,
+                                  const InputInfo &Output,
+                                  const InputInfoList &Inputs,
+                                  const ArgList &Args,
+                                  const char *LinkingOutput) const {
+  const auto &ToolChain = static_cast<const Cygwin &>(getToolChain());
+  const Driver &D = ToolChain.getDriver();
+
+  const bool IsIAMCU = ToolChain.getTriple().isOSIAMCU();
+  const bool IsVE = ToolChain.getTriple().isVE();
+  const bool IsStaticPIE = getStaticPIE(Args, ToolChain);
+  const bool IsStatic = getStatic(Args);
+
+  ArgStringList CmdArgs;
+
+  // Silence warning for "clang -g foo.o -o foo"
+  Args.ClaimAllArgs(options::OPT_g_Group);
+  // and "clang -emit-llvm foo.o -o foo"
+  Args.ClaimAllArgs(options::OPT_emit_llvm);
+  // and for "clang -w foo.o -o foo". Other warning options are already
+  // handled somewhere else.
+  Args.ClaimAllArgs(options::OPT_w);
+
+  if (!D.SysRoot.empty())
+    CmdArgs.push_back(Args.MakeArgString("--sysroot=" + D.SysRoot));
+
+  if (Args.hasArg(options::OPT_s))
+    CmdArgs.push_back("-s");
+
+  CmdArgs.push_back("-m");
+  switch (ToolChain.getArch()) {
+  case llvm::Triple::x86:
+    CmdArgs.push_back("i386pe");
+    break;
+  case llvm::Triple::x86_64:
+    CmdArgs.push_back("i386pep");
+    break;
+  case llvm::Triple::arm:
+  case llvm::Triple::thumb:
+    // FIXME: this is incorrect for WinCE
+    CmdArgs.push_back("thumb2pe");
+    break;
+  case llvm::Triple::aarch64:
+    if (ToolChain.getEffectiveTriple().isWindowsArm64EC())
+      CmdArgs.push_back("arm64ecpe");
+    else
+      CmdArgs.push_back("arm64pe");
+    break;
+  default:
+    D.Diag(diag::err_target_unknown_triple) << ToolChain.getEffectiveTriple().str();
+  }
+
+  CmdArgs.push_back("--wrap=_Znwm");
+  CmdArgs.push_back("--wrap=_Znam");
+  CmdArgs.push_back("--wrap=_ZdlPv");
+  CmdArgs.push_back("--wrap=_ZdaPv");
+  CmdArgs.push_back("--wrap=_ZnwmRKSt9nothrow_t");
+  CmdArgs.push_back("--wrap=_ZnamRKSt9nothrow_t");
+  CmdArgs.push_back("--wrap=_ZdlPvRKSt9nothrow_t");
+  CmdArgs.push_back("--wrap=_ZdaPvRKSt9nothrow_t");
+
+  const bool IsShared = Args.hasArg(options::OPT_shared);
+  if (IsShared)
+    CmdArgs.push_back("-shared");
+  bool IsPIE = false;
+  if (IsStaticPIE) {
+    CmdArgs.push_back("-static");
+    CmdArgs.push_back("-pie");
+    CmdArgs.push_back("--no-dynamic-linker");
+    CmdArgs.push_back("-z");
+    CmdArgs.push_back("text");
+  } else if (IsStatic) {
+    CmdArgs.push_back("-static");
+  } else if (!Args.hasArg(options::OPT_r)) {
+    if (Args.hasArg(options::OPT_rdynamic))
+      CmdArgs.push_back("-export-dynamic");
+    if (!IsShared) {
+      IsPIE = Args.hasFlag(options::OPT_pie, options::OPT_no_pie,
+                           ToolChain.isPIEDefault(Args));
+      if (IsPIE)
+        CmdArgs.push_back("-pie");
+    }
+  }
+
+  CmdArgs.push_back("-o");
+  CmdArgs.push_back(Output.getFilename());
+
+  if (!Args.hasArg(options::OPT_nostdlib, options::OPT_nostartfiles,
+                   options::OPT_r)) {
+    if (IsVE) {
+      CmdArgs.push_back("-z");
+      CmdArgs.push_back("max-page-size=0x4000000");
+    }
+
+    if (IsShared) {
+      CmdArgs.push_back("-e");
+      CmdArgs.push_back("_cygwin_dll_entry");
+      CmdArgs.push_back("--enable-auto-image-base");
+    }
+
+    if (!IsShared)
+      CmdArgs.push_back(Args.MakeArgString(ToolChain.GetFilePath("crt0.o")));
+    if (ToolChain.GetRuntimeLibType(Args) == ToolChain::RLT_CompilerRT) {
+      std::string crtbegin = ToolChain.getCompilerRT(Args, "crtbegin",
+                                                     ToolChain::FT_Object);
+      if (ToolChain.getVFS().exists(crtbegin)) {
+        std::string P;
+        P = crtbegin;
+        CmdArgs.push_back(Args.MakeArgString(P));
+      }
+    }
+    if (IsShared)
+      CmdArgs.push_back(Args.MakeArgString(ToolChain.GetFilePath("crtbeginS.o")));
+    else
+      CmdArgs.push_back(Args.MakeArgString(ToolChain.GetFilePath("crtbegin.o")));
+
+    // Add crtfastmath.o if available and fast math is enabled.
+    ToolChain.addFastMathRuntimeIfAvailable(Args, CmdArgs);
+  }
+
+  Args.addAllArgs(CmdArgs, {options::OPT_L, options::OPT_u});
+
+  ToolChain.AddFilePathLibArgs(Args, CmdArgs);
+
+  CmdArgs.push_back(Args.MakeArgString(
+      "-L" + ToolChain.getGCCInstallation().getInstallPath()));
+
+  if (D.isUsingLTO()) {
+    assert(!Inputs.empty() && "Must have at least one input.");
+    // Find the first filename InputInfo object.
+    auto Input = llvm::find_if(
+        Inputs, [](const InputInfo &II) -> bool { return II.isFilename(); });
+    if (Input == Inputs.end())
+      // For a very rare case, all of the inputs to the linker are
+      // InputArg. If that happens, just use the first InputInfo.
+      Input = Inputs.begin();
+
+    tools::addLTOOptions(ToolChain, Args, CmdArgs, Output, *Input,
+                  D.getLTOMode() == LTOK_Thin);
+  }
+
+  if (Args.hasArg(options::OPT_Z_Xlinker__no_demangle))
+    CmdArgs.push_back("--no-demangle");
+
+  bool NeedsSanitizerDeps = tools::addSanitizerRuntimes(ToolChain, Args, CmdArgs);
+  bool NeedsXRayDeps = tools::addXRayRuntime(ToolChain, Args, CmdArgs);
+  tools::addLinkerCompressDebugSectionsOption(ToolChain, Args, CmdArgs);
+  tools::AddLinkerInputs(ToolChain, Inputs, Args, CmdArgs, JA);
+
+  tools::addHIPRuntimeLibArgs(ToolChain, C, Args, CmdArgs);
+
+  // The profile runtime also needs access to system libraries.
+  getToolChain().addProfileRTLibs(Args, CmdArgs);
+
+  if (D.CCCIsCXX() &&
+      !Args.hasArg(options::OPT_nostdlib, options::OPT_nodefaultlibs,
+                   options::OPT_r)) {
+    if (ToolChain.ShouldLinkCXXStdlib(Args)) {
+      bool OnlyLibstdcxxStatic = Args.hasArg(options::OPT_static_libstdcxx) &&
+                                 !Args.hasArg(options::OPT_static);
+      if (OnlyLibstdcxxStatic)
+        CmdArgs.push_back("-Bstatic");
+      ToolChain.AddCXXStdlibLibArgs(Args, CmdArgs);
+      if (OnlyLibstdcxxStatic)
+        CmdArgs.push_back("-Bdynamic");
+    }
+    CmdArgs.push_back("-lm");
+  }
+
+  // Silence warnings when linking C code with a C++ '-stdlib' argument.
+  Args.ClaimAllArgs(options::OPT_stdlib_EQ);
+
+  // Additional linker set-up and flags for Fortran. This is required in order
+  // to generate executables. As Fortran runtime depends on the C runtime,
+  // these dependencies need to be listed before the C runtime below (i.e.
+  // AddRunTimeLibs).
+  if (D.IsFlangMode() &&
+      !Args.hasArg(options::OPT_nostdlib, options::OPT_nodefaultlibs)) {
+    tools::addFortranRuntimeLibraryPath(ToolChain, Args, CmdArgs);
+    tools::addFortranRuntimeLibs(ToolChain, Args, CmdArgs);
+    CmdArgs.push_back("-lm");
+  }
+
+  if (!Args.hasArg(options::OPT_nostdlib, options::OPT_r)) {
+    if (!Args.hasArg(options::OPT_nodefaultlibs)) {
+      if (IsStatic || IsStaticPIE)
+        CmdArgs.push_back("--start-group");
+
+      if (NeedsSanitizerDeps)
+        tools::linkSanitizerRuntimeDeps(ToolChain, Args, CmdArgs);
+
+      if (NeedsXRayDeps)
+        tools::linkXRayRuntimeDeps(ToolChain, Args, CmdArgs);
+
+      bool WantPthread = Args.hasArg(options::OPT_pthread) ||
+                         Args.hasArg(options::OPT_pthreads);
+
+      // Use the static OpenMP runtime with -static-openmp
+      bool StaticOpenMP = Args.hasArg(options::OPT_static_openmp) &&
+                          !Args.hasArg(options::OPT_static);
+
+      // FIXME: Only pass GompNeedsRT = true for platforms with libgomp that
+      // require librt. Most modern Linux platforms do, but some may not.
+      if (tools::addOpenMPRuntime(C, CmdArgs, ToolChain, Args, StaticOpenMP,
+                           JA.isHostOffloading(Action::OFK_OpenMP),
+                           /* GompNeedsRT= */ true))
+        // OpenMP runtimes implies pthreads when using the GNU toolchain.
+        // FIXME: Does this really make sense for all GNU toolchains?
+        WantPthread = true;
+
+      tools::AddRunTimeLibs(ToolChain, D, CmdArgs, Args);
+
+      if (WantPthread)
+        CmdArgs.push_back("-lpthread");
+
+      if (Args.hasArg(options::OPT_fsplit_stack))
+        CmdArgs.push_back("--wrap=pthread_create");
+
+      if (!Args.hasArg(options::OPT_nolibc))
+        CmdArgs.push_back("-lc");
+
+      // Cygwin specific
+      CmdArgs.push_back("-lcygwin");
+      CmdArgs.push_back("-ladvapi32");
+      CmdArgs.push_back("-lshell32");
+      CmdArgs.push_back("-luser32");
+      CmdArgs.push_back("-lkernel32");
+
+      // Add IAMCU specific libs, if needed.
+      if (IsIAMCU)
+        CmdArgs.push_back("-lgloss");
+
+      if (IsStatic || IsStaticPIE)
+        CmdArgs.push_back("--end-group");
+      else
+        tools::AddRunTimeLibs(ToolChain, D, CmdArgs, Args);
+
+      // Add IAMCU specific libs (outside the group), if needed.
+      if (IsIAMCU) {
+        CmdArgs.push_back("--as-needed");
+        CmdArgs.push_back("-lsoftfp");
+        CmdArgs.push_back("--no-as-needed");
+      }
+    }
+
+    if (!Args.hasArg(options::OPT_nostartfiles) && !IsIAMCU) {
+      if (ToolChain.GetRuntimeLibType(Args) == ToolChain::RLT_CompilerRT) {
+        std::string crtend = ToolChain.getCompilerRT(Args, "crtend",
+                                                     ToolChain::FT_Object);
+        if (ToolChain.getVFS().exists(crtend)) {
+          std::string P;
+          P = crtend;
+          CmdArgs.push_back(Args.MakeArgString(P));
+        }
+      }
+      CmdArgs.push_back(Args.MakeArgString(ToolChain.GetFilePath("crtend.o")));
+    }
+  }
+
+  Args.addAllArgs(CmdArgs, {options::OPT_T, options::OPT_t});
+
+  const char *Exec = Args.MakeArgString(ToolChain.GetLinkerPath());
+  C.addCommand(std::make_unique<Command>(JA, *this,
+                                         ResponseFileSupport::AtFileCurCP(),
+                                         Exec, CmdArgs, Inputs, Output));
+}
+
+auto Cygwin::buildLinker() const -> Tool * { return new cygwin::Linker(*this); }
diff --git a/clang/lib/Driver/ToolChains/Cygwin.h b/clang/lib/Driver/ToolChains/Cygwin.h
index d2f72c10c3b01..b76069fba6741 100644
--- a/clang/lib/Driver/ToolChains/Cygwin.h
+++ b/clang/lib/Driver/ToolChains/Cygwin.h
@@ -27,7 +27,28 @@ class LLVM_LIBRARY_VISIBILITY Cygwin : public Generic_GCC {
   void
   AddClangSystemIncludeArgs(const llvm::opt::ArgList &DriverArgs,
                             llvm::opt::ArgStringList &CC1Args) const override;
+
+  const GCCInstallationDetector &
+  getGCCInstallation() const { return GCCInstallation; };
+
+protected:
+  Tool *buildLinker() const override;
+};
+
+namespace cygwin {
+class LLVM_LIBRARY_VISIBILITY Linker final : public Tool {
+public:
+  Linker(const ToolChain &TC) : Tool("cygwin::Linker", "linker", TC) {}
+
+  bool hasIntegratedCPP() const override { return false; }
+  bool isLinkJob() const override { return true; }
+
+  void ConstructJob(Compilation &C, const JobAction &JA,
+                    const InputInfo &Output, const InputInfoList &Inputs,
+                    const llvm::opt::ArgList &TCArgs,
+                    const char *LinkingOutput) const override;
 };
+} // end namespace cygwin
 
 } // end namespace toolchains
 } // end namespace driver
diff --git a/clang/test/Driver/Inputs/basic_cross_cygwin_tree/usr/lib/gcc/i686-pc-msys/10/crtend.o b/clang/test/Driver/Inputs/basic_cross_cygwin_tree/usr/lib/gcc/i686-pc-msys/10/crtend.o
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/clang/test/Driver/Inputs/basic_cross_cygwin_tree/usr/lib/gcc/x86_64-pc-cygwin/10/crtend.o b/clang/test/Driver/Inputs/basic_cross_cygwin_tree/usr/lib/gcc/x86_64-pc-cygwin/10/crtend.o
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/clang/test/Driver/Inputs/basic_cygwin_tree/usr/lib/crt0.o b/clang/test/Driver/Inputs/basic_cygwin_tree/usr/lib/crt0.o
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/clang/test/Driver/Inputs/basic_cygwin_tree/usr/lib/gcc/i686-pc-cygwin/10/crtend.o b/clang/test/Driver/Inputs/basic_cygwin_tree/usr/lib/gcc/i686-pc-cygwin/10/crtend.o
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/clang/test/Driver/Inputs/basic_cygwin_tree/usr/lib/gcc/x86_64-pc-msys/10/crtend.o b/clang/test/Driver/Inputs/basic_cygwin_tree/usr/lib/gcc/x86_64-pc-msys/10/crtend.o
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/clang/test/Driver/cygwin.cpp b/clang/test/Driver/cygwin.cpp
index dd75c48ddc6b3..8677e84c033fd 100644
--- a/clang/test/Driver/cygwin.cpp
+++ b/clang/test/Driver/cygwin.cpp
@@ -15,19 +15,19 @@
 // CHECK-SAME: {{^}} "-internal-externc-isystem" "[[SYSROOT]]/usr/include/w32api"
 // CHECK-SAME: "-femulated-tls"
 // CHECK-SAME: "-exception-model=dwarf"
-// CHECK:      "{{.*}}gcc{{(\.exe)?}}"
-// CHECK-SAME: "-m32"
+// CHECK:      "{{.*}}ld{{(\.exe)?}}"
+// CHECK-SAME: "-m" "i386pe"
 
 // RUN: %clang -### %s --target=i686-pc-cygwin --sysroot=%S/Inputs/basic_cygwin_tree \
 // RUN:   --stdlib=platform -static 2>&1 | FileCheck --check-prefix=CHECK-STATIC %s
 // CHECK-STATIC:      "-cc1" "-triple" "i686-pc-windows-cygnus"
 // CHECK-STATIC-SAME: "-static-define"
-// CHECK-STATIC:      "{{.*}}gcc{{(\.exe)?}}"
+// CHECK-STATIC:      "{{.*}}ld{{(\.exe)?}}"
 // CHECK-STATIC-SAME: "-static"
 
 // RUN: %clang -### %s --target=i686-pc-cygwin --sysroot=%S/Inputs/basic_cygwin_tree \
 // RUN:   -shared 2>&1 | FileCheck --check-prefix=CHECK-SHARED %s
-// CHECK-SHARED:      "{{.*}}gcc{{(\.exe)?}}"
+// CHECK-SHARED:      "{{.*}}ld{{(\.exe)?}}"
 // CHECK-SHARED-SAME: "-shared"
 
 // RUN: %clang -### -o %t %s 2>&1 -no-integrated-as -fuse-ld=ld \
@@ -54,19 +54,19 @@
 // CHECK-64-SAME: {{^}} "-internal-externc-isystem" "[[SYSROOT]]/usr/include/w32api"
 // CHECK-64-SAME: "-femulated-tls"
 // CHECK-64-SAME: "-exception-model=seh"
-// CHECK-64:      "{{.*}}gcc{{(\.exe)?}}"
-// CHECK-64-SAME: "-m64"
+// CHECK-64:      "{{.*}}ld{{(\.exe)?}}"
+// CHECK-64-SAME: "-m" "i386pep"
 
 // RUN: %clang -### %s --target=x86_64-pc-cygwin --sysroot=%S/Inputs/basic_cygwin_tree \
 // RUN:   --stdlib=platform -static 2>&1 | FileCheck --check-prefix=CHECK-64-STATIC %s
 // CHECK-64-STATIC:      "-cc1" "-triple" "x86_64-pc-windows-cygnus"
 // CHECK-64-STATIC-SAME: "-static-define"
-// CHECK-64-STATIC:      "{{.*}}gcc{{(\.exe)?}}"
+// CHECK-64-STATIC:      "{{.*}}ld{{(\.exe)?}}"
 // CHECK-64-STATIC-SAME: "-static"
 
 // RUN: %clang -### %s --target=x86_64-pc-cygwin --sysroot=%S/Inputs/basic_cygwin_tree \
 // RUN:   -shared 2>&1 | FileCheck --check-prefix=CHECK-64-SHARED %s
-// CHECK-64-SHARED:      "{{.*}}gcc{{(\.exe)?}}"
+// CHECK-64-SHARED:      "{{.*}}ld{{(\.exe)?}}"
 // CHECK-64-SHARED-SAME: "-shared"
 
 // RUN: %clang -### -o %t %s 2>&1 -no-integrated-as -fuse-ld=ld \

@llvmbot
Copy link
Member

llvmbot commented Jul 10, 2025

@llvm/pr-subscribers-clang-driver

Author: None (tyan0)

Changes

...so that libc++, compiler-rt, and libunwind can be used by the options: -stdlib=libc++, -rtlib=compiler-rt, and -unwindlib=libunwind respectively. Along with this change, the test for this driver is also trimmed a bit.


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

9 Files Affected:

  • (modified) clang/lib/Driver/ToolChain.cpp (+3)
  • (modified) clang/lib/Driver/ToolChains/Cygwin.cpp (+289)
  • (modified) clang/lib/Driver/ToolChains/Cygwin.h (+21)
  • (added) clang/test/Driver/Inputs/basic_cross_cygwin_tree/usr/lib/gcc/i686-pc-msys/10/crtend.o ()
  • (added) clang/test/Driver/Inputs/basic_cross_cygwin_tree/usr/lib/gcc/x86_64-pc-cygwin/10/crtend.o ()
  • (added) clang/test/Driver/Inputs/basic_cygwin_tree/usr/lib/crt0.o ()
  • (added) clang/test/Driver/Inputs/basic_cygwin_tree/usr/lib/gcc/i686-pc-cygwin/10/crtend.o ()
  • (added) clang/test/Driver/Inputs/basic_cygwin_tree/usr/lib/gcc/x86_64-pc-msys/10/crtend.o ()
  • (modified) clang/test/Driver/cygwin.cpp (+8-8)
diff --git a/clang/lib/Driver/ToolChain.cpp b/clang/lib/Driver/ToolChain.cpp
index 3f9b808b2722e..3c9148ee51fd0 100644
--- a/clang/lib/Driver/ToolChain.cpp
+++ b/clang/lib/Driver/ToolChain.cpp
@@ -684,6 +684,8 @@ static StringRef getArchNameForCompilerRTLib(const ToolChain &TC,
 StringRef ToolChain::getOSLibName() const {
   if (Triple.isOSDarwin())
     return "darwin";
+  if (Triple.isWindowsCygwinEnvironment())
+    return "cygwin";
 
   switch (Triple.getOS()) {
   case llvm::Triple::FreeBSD:
@@ -1504,6 +1506,7 @@ void ToolChain::AddCXXStdlibLibArgs(const ArgList &Args,
   switch (Type) {
   case ToolChain::CST_Libcxx:
     CmdArgs.push_back("-lc++");
+    CmdArgs.push_back("-lc++abi");
     if (Args.hasArg(options::OPT_fexperimental_library))
       CmdArgs.push_back("-lc++experimental");
     break;
diff --git a/clang/lib/Driver/ToolChains/Cygwin.cpp b/clang/lib/Driver/ToolChains/Cygwin.cpp
index d9c16347daa34..82a88065b0c9d 100644
--- a/clang/lib/Driver/ToolChains/Cygwin.cpp
+++ b/clang/lib/Driver/ToolChains/Cygwin.cpp
@@ -9,6 +9,7 @@
 #include "Cygwin.h"
 #include "clang/Config/config.h"
 #include "clang/Driver/CommonArgs.h"
+#include "clang/Driver/Compilation.h"
 #include "clang/Driver/Driver.h"
 #include "clang/Driver/Options.h"
 #include "llvm/Support/Path.h"
@@ -30,6 +31,8 @@ Cygwin::Cygwin(const Driver &D, const llvm::Triple &Triple, const ArgList &Args)
   Generic_GCC::PushPPaths(PPaths);
 
   path_list &Paths = getFilePaths();
+  if (GCCInstallation.isValid())
+    Paths.push_back(GCCInstallation.getInstallPath().str());
 
   Generic_GCC::AddMultiarchPaths(D, SysRoot, "lib", Paths);
 
@@ -107,3 +110,289 @@ void Cygwin::AddClangSystemIncludeArgs(const ArgList &DriverArgs,
   addExternCSystemInclude(DriverArgs, CC1Args, SysRoot + "/usr/include");
   addExternCSystemInclude(DriverArgs, CC1Args, SysRoot + "/usr/include/w32api");
 }
+
+static bool getStaticPIE(const ArgList &Args, const ToolChain &TC) {
+  bool HasStaticPIE = Args.hasArg(options::OPT_static_pie);
+  if (HasStaticPIE && Args.hasArg(options::OPT_no_pie)) {
+    const Driver &D = TC.getDriver();
+    const llvm::opt::OptTable &Opts = D.getOpts();
+    StringRef StaticPIEName = Opts.getOptionName(options::OPT_static_pie);
+    StringRef NoPIEName = Opts.getOptionName(options::OPT_nopie);
+    D.Diag(diag::err_drv_cannot_mix_options) << StaticPIEName << NoPIEName;
+  }
+  return HasStaticPIE;
+}
+
+static bool getStatic(const ArgList &Args) {
+  return Args.hasArg(options::OPT_static) &&
+      !Args.hasArg(options::OPT_static_pie);
+}
+
+void cygwin::Linker::ConstructJob(Compilation &C, const JobAction &JA,
+                                  const InputInfo &Output,
+                                  const InputInfoList &Inputs,
+                                  const ArgList &Args,
+                                  const char *LinkingOutput) const {
+  const auto &ToolChain = static_cast<const Cygwin &>(getToolChain());
+  const Driver &D = ToolChain.getDriver();
+
+  const bool IsIAMCU = ToolChain.getTriple().isOSIAMCU();
+  const bool IsVE = ToolChain.getTriple().isVE();
+  const bool IsStaticPIE = getStaticPIE(Args, ToolChain);
+  const bool IsStatic = getStatic(Args);
+
+  ArgStringList CmdArgs;
+
+  // Silence warning for "clang -g foo.o -o foo"
+  Args.ClaimAllArgs(options::OPT_g_Group);
+  // and "clang -emit-llvm foo.o -o foo"
+  Args.ClaimAllArgs(options::OPT_emit_llvm);
+  // and for "clang -w foo.o -o foo". Other warning options are already
+  // handled somewhere else.
+  Args.ClaimAllArgs(options::OPT_w);
+
+  if (!D.SysRoot.empty())
+    CmdArgs.push_back(Args.MakeArgString("--sysroot=" + D.SysRoot));
+
+  if (Args.hasArg(options::OPT_s))
+    CmdArgs.push_back("-s");
+
+  CmdArgs.push_back("-m");
+  switch (ToolChain.getArch()) {
+  case llvm::Triple::x86:
+    CmdArgs.push_back("i386pe");
+    break;
+  case llvm::Triple::x86_64:
+    CmdArgs.push_back("i386pep");
+    break;
+  case llvm::Triple::arm:
+  case llvm::Triple::thumb:
+    // FIXME: this is incorrect for WinCE
+    CmdArgs.push_back("thumb2pe");
+    break;
+  case llvm::Triple::aarch64:
+    if (ToolChain.getEffectiveTriple().isWindowsArm64EC())
+      CmdArgs.push_back("arm64ecpe");
+    else
+      CmdArgs.push_back("arm64pe");
+    break;
+  default:
+    D.Diag(diag::err_target_unknown_triple) << ToolChain.getEffectiveTriple().str();
+  }
+
+  CmdArgs.push_back("--wrap=_Znwm");
+  CmdArgs.push_back("--wrap=_Znam");
+  CmdArgs.push_back("--wrap=_ZdlPv");
+  CmdArgs.push_back("--wrap=_ZdaPv");
+  CmdArgs.push_back("--wrap=_ZnwmRKSt9nothrow_t");
+  CmdArgs.push_back("--wrap=_ZnamRKSt9nothrow_t");
+  CmdArgs.push_back("--wrap=_ZdlPvRKSt9nothrow_t");
+  CmdArgs.push_back("--wrap=_ZdaPvRKSt9nothrow_t");
+
+  const bool IsShared = Args.hasArg(options::OPT_shared);
+  if (IsShared)
+    CmdArgs.push_back("-shared");
+  bool IsPIE = false;
+  if (IsStaticPIE) {
+    CmdArgs.push_back("-static");
+    CmdArgs.push_back("-pie");
+    CmdArgs.push_back("--no-dynamic-linker");
+    CmdArgs.push_back("-z");
+    CmdArgs.push_back("text");
+  } else if (IsStatic) {
+    CmdArgs.push_back("-static");
+  } else if (!Args.hasArg(options::OPT_r)) {
+    if (Args.hasArg(options::OPT_rdynamic))
+      CmdArgs.push_back("-export-dynamic");
+    if (!IsShared) {
+      IsPIE = Args.hasFlag(options::OPT_pie, options::OPT_no_pie,
+                           ToolChain.isPIEDefault(Args));
+      if (IsPIE)
+        CmdArgs.push_back("-pie");
+    }
+  }
+
+  CmdArgs.push_back("-o");
+  CmdArgs.push_back(Output.getFilename());
+
+  if (!Args.hasArg(options::OPT_nostdlib, options::OPT_nostartfiles,
+                   options::OPT_r)) {
+    if (IsVE) {
+      CmdArgs.push_back("-z");
+      CmdArgs.push_back("max-page-size=0x4000000");
+    }
+
+    if (IsShared) {
+      CmdArgs.push_back("-e");
+      CmdArgs.push_back("_cygwin_dll_entry");
+      CmdArgs.push_back("--enable-auto-image-base");
+    }
+
+    if (!IsShared)
+      CmdArgs.push_back(Args.MakeArgString(ToolChain.GetFilePath("crt0.o")));
+    if (ToolChain.GetRuntimeLibType(Args) == ToolChain::RLT_CompilerRT) {
+      std::string crtbegin = ToolChain.getCompilerRT(Args, "crtbegin",
+                                                     ToolChain::FT_Object);
+      if (ToolChain.getVFS().exists(crtbegin)) {
+        std::string P;
+        P = crtbegin;
+        CmdArgs.push_back(Args.MakeArgString(P));
+      }
+    }
+    if (IsShared)
+      CmdArgs.push_back(Args.MakeArgString(ToolChain.GetFilePath("crtbeginS.o")));
+    else
+      CmdArgs.push_back(Args.MakeArgString(ToolChain.GetFilePath("crtbegin.o")));
+
+    // Add crtfastmath.o if available and fast math is enabled.
+    ToolChain.addFastMathRuntimeIfAvailable(Args, CmdArgs);
+  }
+
+  Args.addAllArgs(CmdArgs, {options::OPT_L, options::OPT_u});
+
+  ToolChain.AddFilePathLibArgs(Args, CmdArgs);
+
+  CmdArgs.push_back(Args.MakeArgString(
+      "-L" + ToolChain.getGCCInstallation().getInstallPath()));
+
+  if (D.isUsingLTO()) {
+    assert(!Inputs.empty() && "Must have at least one input.");
+    // Find the first filename InputInfo object.
+    auto Input = llvm::find_if(
+        Inputs, [](const InputInfo &II) -> bool { return II.isFilename(); });
+    if (Input == Inputs.end())
+      // For a very rare case, all of the inputs to the linker are
+      // InputArg. If that happens, just use the first InputInfo.
+      Input = Inputs.begin();
+
+    tools::addLTOOptions(ToolChain, Args, CmdArgs, Output, *Input,
+                  D.getLTOMode() == LTOK_Thin);
+  }
+
+  if (Args.hasArg(options::OPT_Z_Xlinker__no_demangle))
+    CmdArgs.push_back("--no-demangle");
+
+  bool NeedsSanitizerDeps = tools::addSanitizerRuntimes(ToolChain, Args, CmdArgs);
+  bool NeedsXRayDeps = tools::addXRayRuntime(ToolChain, Args, CmdArgs);
+  tools::addLinkerCompressDebugSectionsOption(ToolChain, Args, CmdArgs);
+  tools::AddLinkerInputs(ToolChain, Inputs, Args, CmdArgs, JA);
+
+  tools::addHIPRuntimeLibArgs(ToolChain, C, Args, CmdArgs);
+
+  // The profile runtime also needs access to system libraries.
+  getToolChain().addProfileRTLibs(Args, CmdArgs);
+
+  if (D.CCCIsCXX() &&
+      !Args.hasArg(options::OPT_nostdlib, options::OPT_nodefaultlibs,
+                   options::OPT_r)) {
+    if (ToolChain.ShouldLinkCXXStdlib(Args)) {
+      bool OnlyLibstdcxxStatic = Args.hasArg(options::OPT_static_libstdcxx) &&
+                                 !Args.hasArg(options::OPT_static);
+      if (OnlyLibstdcxxStatic)
+        CmdArgs.push_back("-Bstatic");
+      ToolChain.AddCXXStdlibLibArgs(Args, CmdArgs);
+      if (OnlyLibstdcxxStatic)
+        CmdArgs.push_back("-Bdynamic");
+    }
+    CmdArgs.push_back("-lm");
+  }
+
+  // Silence warnings when linking C code with a C++ '-stdlib' argument.
+  Args.ClaimAllArgs(options::OPT_stdlib_EQ);
+
+  // Additional linker set-up and flags for Fortran. This is required in order
+  // to generate executables. As Fortran runtime depends on the C runtime,
+  // these dependencies need to be listed before the C runtime below (i.e.
+  // AddRunTimeLibs).
+  if (D.IsFlangMode() &&
+      !Args.hasArg(options::OPT_nostdlib, options::OPT_nodefaultlibs)) {
+    tools::addFortranRuntimeLibraryPath(ToolChain, Args, CmdArgs);
+    tools::addFortranRuntimeLibs(ToolChain, Args, CmdArgs);
+    CmdArgs.push_back("-lm");
+  }
+
+  if (!Args.hasArg(options::OPT_nostdlib, options::OPT_r)) {
+    if (!Args.hasArg(options::OPT_nodefaultlibs)) {
+      if (IsStatic || IsStaticPIE)
+        CmdArgs.push_back("--start-group");
+
+      if (NeedsSanitizerDeps)
+        tools::linkSanitizerRuntimeDeps(ToolChain, Args, CmdArgs);
+
+      if (NeedsXRayDeps)
+        tools::linkXRayRuntimeDeps(ToolChain, Args, CmdArgs);
+
+      bool WantPthread = Args.hasArg(options::OPT_pthread) ||
+                         Args.hasArg(options::OPT_pthreads);
+
+      // Use the static OpenMP runtime with -static-openmp
+      bool StaticOpenMP = Args.hasArg(options::OPT_static_openmp) &&
+                          !Args.hasArg(options::OPT_static);
+
+      // FIXME: Only pass GompNeedsRT = true for platforms with libgomp that
+      // require librt. Most modern Linux platforms do, but some may not.
+      if (tools::addOpenMPRuntime(C, CmdArgs, ToolChain, Args, StaticOpenMP,
+                           JA.isHostOffloading(Action::OFK_OpenMP),
+                           /* GompNeedsRT= */ true))
+        // OpenMP runtimes implies pthreads when using the GNU toolchain.
+        // FIXME: Does this really make sense for all GNU toolchains?
+        WantPthread = true;
+
+      tools::AddRunTimeLibs(ToolChain, D, CmdArgs, Args);
+
+      if (WantPthread)
+        CmdArgs.push_back("-lpthread");
+
+      if (Args.hasArg(options::OPT_fsplit_stack))
+        CmdArgs.push_back("--wrap=pthread_create");
+
+      if (!Args.hasArg(options::OPT_nolibc))
+        CmdArgs.push_back("-lc");
+
+      // Cygwin specific
+      CmdArgs.push_back("-lcygwin");
+      CmdArgs.push_back("-ladvapi32");
+      CmdArgs.push_back("-lshell32");
+      CmdArgs.push_back("-luser32");
+      CmdArgs.push_back("-lkernel32");
+
+      // Add IAMCU specific libs, if needed.
+      if (IsIAMCU)
+        CmdArgs.push_back("-lgloss");
+
+      if (IsStatic || IsStaticPIE)
+        CmdArgs.push_back("--end-group");
+      else
+        tools::AddRunTimeLibs(ToolChain, D, CmdArgs, Args);
+
+      // Add IAMCU specific libs (outside the group), if needed.
+      if (IsIAMCU) {
+        CmdArgs.push_back("--as-needed");
+        CmdArgs.push_back("-lsoftfp");
+        CmdArgs.push_back("--no-as-needed");
+      }
+    }
+
+    if (!Args.hasArg(options::OPT_nostartfiles) && !IsIAMCU) {
+      if (ToolChain.GetRuntimeLibType(Args) == ToolChain::RLT_CompilerRT) {
+        std::string crtend = ToolChain.getCompilerRT(Args, "crtend",
+                                                     ToolChain::FT_Object);
+        if (ToolChain.getVFS().exists(crtend)) {
+          std::string P;
+          P = crtend;
+          CmdArgs.push_back(Args.MakeArgString(P));
+        }
+      }
+      CmdArgs.push_back(Args.MakeArgString(ToolChain.GetFilePath("crtend.o")));
+    }
+  }
+
+  Args.addAllArgs(CmdArgs, {options::OPT_T, options::OPT_t});
+
+  const char *Exec = Args.MakeArgString(ToolChain.GetLinkerPath());
+  C.addCommand(std::make_unique<Command>(JA, *this,
+                                         ResponseFileSupport::AtFileCurCP(),
+                                         Exec, CmdArgs, Inputs, Output));
+}
+
+auto Cygwin::buildLinker() const -> Tool * { return new cygwin::Linker(*this); }
diff --git a/clang/lib/Driver/ToolChains/Cygwin.h b/clang/lib/Driver/ToolChains/Cygwin.h
index d2f72c10c3b01..b76069fba6741 100644
--- a/clang/lib/Driver/ToolChains/Cygwin.h
+++ b/clang/lib/Driver/ToolChains/Cygwin.h
@@ -27,7 +27,28 @@ class LLVM_LIBRARY_VISIBILITY Cygwin : public Generic_GCC {
   void
   AddClangSystemIncludeArgs(const llvm::opt::ArgList &DriverArgs,
                             llvm::opt::ArgStringList &CC1Args) const override;
+
+  const GCCInstallationDetector &
+  getGCCInstallation() const { return GCCInstallation; };
+
+protected:
+  Tool *buildLinker() const override;
+};
+
+namespace cygwin {
+class LLVM_LIBRARY_VISIBILITY Linker final : public Tool {
+public:
+  Linker(const ToolChain &TC) : Tool("cygwin::Linker", "linker", TC) {}
+
+  bool hasIntegratedCPP() const override { return false; }
+  bool isLinkJob() const override { return true; }
+
+  void ConstructJob(Compilation &C, const JobAction &JA,
+                    const InputInfo &Output, const InputInfoList &Inputs,
+                    const llvm::opt::ArgList &TCArgs,
+                    const char *LinkingOutput) const override;
 };
+} // end namespace cygwin
 
 } // end namespace toolchains
 } // end namespace driver
diff --git a/clang/test/Driver/Inputs/basic_cross_cygwin_tree/usr/lib/gcc/i686-pc-msys/10/crtend.o b/clang/test/Driver/Inputs/basic_cross_cygwin_tree/usr/lib/gcc/i686-pc-msys/10/crtend.o
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/clang/test/Driver/Inputs/basic_cross_cygwin_tree/usr/lib/gcc/x86_64-pc-cygwin/10/crtend.o b/clang/test/Driver/Inputs/basic_cross_cygwin_tree/usr/lib/gcc/x86_64-pc-cygwin/10/crtend.o
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/clang/test/Driver/Inputs/basic_cygwin_tree/usr/lib/crt0.o b/clang/test/Driver/Inputs/basic_cygwin_tree/usr/lib/crt0.o
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/clang/test/Driver/Inputs/basic_cygwin_tree/usr/lib/gcc/i686-pc-cygwin/10/crtend.o b/clang/test/Driver/Inputs/basic_cygwin_tree/usr/lib/gcc/i686-pc-cygwin/10/crtend.o
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/clang/test/Driver/Inputs/basic_cygwin_tree/usr/lib/gcc/x86_64-pc-msys/10/crtend.o b/clang/test/Driver/Inputs/basic_cygwin_tree/usr/lib/gcc/x86_64-pc-msys/10/crtend.o
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/clang/test/Driver/cygwin.cpp b/clang/test/Driver/cygwin.cpp
index dd75c48ddc6b3..8677e84c033fd 100644
--- a/clang/test/Driver/cygwin.cpp
+++ b/clang/test/Driver/cygwin.cpp
@@ -15,19 +15,19 @@
 // CHECK-SAME: {{^}} "-internal-externc-isystem" "[[SYSROOT]]/usr/include/w32api"
 // CHECK-SAME: "-femulated-tls"
 // CHECK-SAME: "-exception-model=dwarf"
-// CHECK:      "{{.*}}gcc{{(\.exe)?}}"
-// CHECK-SAME: "-m32"
+// CHECK:      "{{.*}}ld{{(\.exe)?}}"
+// CHECK-SAME: "-m" "i386pe"
 
 // RUN: %clang -### %s --target=i686-pc-cygwin --sysroot=%S/Inputs/basic_cygwin_tree \
 // RUN:   --stdlib=platform -static 2>&1 | FileCheck --check-prefix=CHECK-STATIC %s
 // CHECK-STATIC:      "-cc1" "-triple" "i686-pc-windows-cygnus"
 // CHECK-STATIC-SAME: "-static-define"
-// CHECK-STATIC:      "{{.*}}gcc{{(\.exe)?}}"
+// CHECK-STATIC:      "{{.*}}ld{{(\.exe)?}}"
 // CHECK-STATIC-SAME: "-static"
 
 // RUN: %clang -### %s --target=i686-pc-cygwin --sysroot=%S/Inputs/basic_cygwin_tree \
 // RUN:   -shared 2>&1 | FileCheck --check-prefix=CHECK-SHARED %s
-// CHECK-SHARED:      "{{.*}}gcc{{(\.exe)?}}"
+// CHECK-SHARED:      "{{.*}}ld{{(\.exe)?}}"
 // CHECK-SHARED-SAME: "-shared"
 
 // RUN: %clang -### -o %t %s 2>&1 -no-integrated-as -fuse-ld=ld \
@@ -54,19 +54,19 @@
 // CHECK-64-SAME: {{^}} "-internal-externc-isystem" "[[SYSROOT]]/usr/include/w32api"
 // CHECK-64-SAME: "-femulated-tls"
 // CHECK-64-SAME: "-exception-model=seh"
-// CHECK-64:      "{{.*}}gcc{{(\.exe)?}}"
-// CHECK-64-SAME: "-m64"
+// CHECK-64:      "{{.*}}ld{{(\.exe)?}}"
+// CHECK-64-SAME: "-m" "i386pep"
 
 // RUN: %clang -### %s --target=x86_64-pc-cygwin --sysroot=%S/Inputs/basic_cygwin_tree \
 // RUN:   --stdlib=platform -static 2>&1 | FileCheck --check-prefix=CHECK-64-STATIC %s
 // CHECK-64-STATIC:      "-cc1" "-triple" "x86_64-pc-windows-cygnus"
 // CHECK-64-STATIC-SAME: "-static-define"
-// CHECK-64-STATIC:      "{{.*}}gcc{{(\.exe)?}}"
+// CHECK-64-STATIC:      "{{.*}}ld{{(\.exe)?}}"
 // CHECK-64-STATIC-SAME: "-static"
 
 // RUN: %clang -### %s --target=x86_64-pc-cygwin --sysroot=%S/Inputs/basic_cygwin_tree \
 // RUN:   -shared 2>&1 | FileCheck --check-prefix=CHECK-64-SHARED %s
-// CHECK-64-SHARED:      "{{.*}}gcc{{(\.exe)?}}"
+// CHECK-64-SHARED:      "{{.*}}ld{{(\.exe)?}}"
 // CHECK-64-SHARED-SAME: "-shared"
 
 // RUN: %clang -### -o %t %s 2>&1 -no-integrated-as -fuse-ld=ld \

Copy link

github-actions bot commented Jul 10, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@jeremyd2019
Copy link
Contributor

Thanks! This was on my mental TODO list but I haven't gotten back to looking at it. When I was thinking about this, I was thinking about modifying the MinGW linker with a couple of virtual functions and deriving from it for a Cygwin linker, to avoid so much code duplication. Maybe @mstorsjo can provide an opinion here?

Also, the cygwin.cpp test was based on hurd.cpp, I'm thinking more of those tests are relevant now.

@@ -1504,6 +1506,7 @@ void ToolChain::AddCXXStdlibLibArgs(const ArgList &Args,
switch (Type) {
case ToolChain::CST_Libcxx:
CmdArgs.push_back("-lc++");
CmdArgs.push_back("-lc++abi");
Copy link
Contributor

@jeremyd2019 jeremyd2019 Jul 10, 2025

Choose a reason for hiding this comment

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

I'm concerned about this change. Why is this not needed already (for other platforms)? Should Cygwin consider merging libc++abi into libc++ as I think MinGW builds do?

Copy link
Author

Choose a reason for hiding this comment

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

I'm not sure. libc++.dll.a depends libc++abi.dll.a. Similarly, libc++.so.1 depends libc++abi.so.1 in linux. Why linux does not need -lc++abi here???

$ ldd /usr/lib/llvm-14/lib/libc++.so.1
        linux-vdso.so.1 (0x00007fffe5f04000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f203b74e000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f203b66e000)
        libc++abi.so.1 => /lib/x86_64-linux-gnu/libc++abi.so.1 (0x00007f203b636000)
        libunwind.so.1 => /lib/x86_64-linux-gnu/libunwind.so.1 (0x00007f203b62b000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f203ba46000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f203b60b000)

Copy link
Contributor

Choose a reason for hiding this comment

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

ELF DSO can embed its dependencies in itself and find them at link time. PE/COFF doesn't have such mechanism for the link time AFAIK.

MinGW package is built with -DLIBCXX_ENABLE_STATIC_ABI_LIBRARY so doesn't need -lc++abi.

Copy link
Author

Choose a reason for hiding this comment

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

Then, how about:

@@ -1504,6 +1506,8 @@ void ToolChain::AddCXXStdlibLibArgs(const ArgList &Args,
   switch (Type) {
   case ToolChain::CST_Libcxx:
     CmdArgs.push_back("-lc++");
+    if (getTriple().isOSWindows())
+      CmdArgs.push_back("-lc++abi");
     if (Args.hasArg(options::OPT_fexperimental_library))
       CmdArgs.push_back("-lc++experimental");
     break;

? if COFF does not support that feature, adding this only for windows sounds reasonable to me.

Copy link
Contributor

Choose a reason for hiding this comment

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

It's packaging policy matter, but it would be better to merge libc++abi in libc++ because libc++ can't link to another ABI library. MinGW does so.

Copy link
Member

Choose a reason for hiding this comment

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

libc++abi and libc++ have circular dependencies - these are fine on ELF platforms, but for PE/COFF they're very problematic. (Theoretically it is possible to link DLLs with circular dependencies, but in practice, we'd rather not do that.)

Therefore, in practice, the most practical solution is to just merge libc++abi into libc++, like we do in all build configurations for mingw. That way, the compiler driver never needs to see or care about libc++abi.

Therefore, I'm against adding a if (getTriple().isOSWindows()) CmdArgs.push_back("-lc++abi"); - that would significantly break all existing mingw build configurations.

@jeremyd2019
Copy link
Contributor

Oh, another proposed implementation to look at: #74933 I was planning to refer to that, MinGW.cpp, and the GCC spec for Cygwin, and see if I could come up with a couple of places where virtual functions could be added to the MinGW::Linker class so that Cygwin::Linker could derive from it and override (I think the "wrap" stuff and list of default libraries)

@kikairoya
Copy link
Contributor

kikairoya commented Jul 10, 2025

Could you let I know output of echo | clang -xc - -v (and some option variants) with patch?

Does this patch consider to use of LLD? ( gcc and clang can use with LLD: -fuse-ld=lld -L/usr/lib/w32api -Wl,--enable-auto-import,--disable-high-entropy-va,--disable-nxcompat,--wrap,_Znwm,--wrap,_Znam,--wrap,_ZdlPv,--wrap,_ZdaPv,--wrap,_ZnwmRKSt9nothrow_t,--wrap,_ZnamRKSt9nothrow_t,--wrap,_ZdlPvRKSt9nothrow_t,--wrap,_ZdaPvRKSt9nothrow_t,-u,_Znwm,-u,_Znam,-u,_ZdlPv,-u,_ZdaPv,-u,_ZnwmRKSt9nothrow_t,-u,_ZnamRKSt9nothrow_t,-u,_ZdlPvRKSt9nothrow_t,-u,_ZdaPvRKSt9nothrow_t )

I was planning to refer to that, MinGW.cpp, and the GCC spec for Cygwin, and see if I could come up with a couple of places where virtual functions could be added to the MinGW::Linker class so that Cygwin::Linker could derive from it and override (I think the "wrap" stuff and list of default libraries)

+1
I think Cygwin-specific driver will not be maintained by others and will diverge and left alone from other platform or components.
But, unfortunately, differences of MinGW and Cygwin are scattered widely so doing that isn't trivial I think.
My hack (copy from MinGW.cpp) may help to see difference between MinGW and Cygwin. https://github.com/kikairoya/llvm-project/blob/cygwin-new-driver/clang/lib/Driver/ToolChains/Cygwin.cpp

@jeremyd2019
Copy link
Contributor

I was planning to refer to that, MinGW.cpp, and the GCC spec for Cygwin, and see if I could come up with a couple of places where virtual functions could be added to the MinGW::Linker class so that Cygwin::Linker could derive from it and override (I think the "wrap" stuff and list of default libraries)

+1 I think Cygwin-specific driver will not be maintained by others and will diverge and left alone from other platform or components. But, unfortunately, differences of MinGW and Cygwin are scattered widely so doing that isn't trivial I think. My hack (copy from MinGW.cpp) may help to see difference between MinGW and Cygwin. https://github.com/kikairoya/llvm-project/blob/cygwin-new-driver/clang/lib/Driver/ToolChains/Cygwin.cpp

I meant just the Linker class. I don't think it's really possible to avoid a dedicated Cygwin driver, but I'm hoping it can keep close to the Generic_GCC base class rather than redoing so much stuff as MinGW did.

@kikairoya
Copy link
Contributor

I meant just the Linker class.

Most of differences are related to triple, recognizing path, library names and linker options so many of them affects to Linker, so it will have amounts of virtual functions or StringRef members.
Even though it seems to be "differential programming", I also prefer it to copying MinGW's implementation (and it's why I didn't make a PR).

...so that libc++, compiler-rt, and libunwind can be used by the
options: -stdlib=libc++, -rtlib=compiler-rt, and -unwindlib=libunwind
respectively. Along with this change, the test for this driver is also
trimmed a bit.

This is a follow-up patch to the commit 52924a2.

Signed-off-by: Takashi Yano <takashi.yano@nifty.ne.jp>
@tyan0
Copy link
Author

tyan0 commented Jul 11, 2025

Could you let I know output of echo | clang -xc - -v (and some option variants) with patch?

$ echo | clang -xc - -v
clang version 20.1.7 (ssh://tyan0@cygwin.com/git/cygwin-packages/clang 9fadb5f0ea7d31bddc623bbd3c6a9beff4d235fc)
Target: x86_64-pc-windows-cygnus
Thread model: posix
InstalledDir: /usr/bin
Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-pc-cygwin/12
Selected GCC installation: /usr/bin/../lib/gcc/x86_64-pc-cygwin/12
Candidate multilib: .;@m64
Selected multilib: .;@m64
 "/usr/bin/clang-20" -cc1 -triple x86_64-pc-windows-cygnus -emit-obj -dumpdir a- -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name - -mrelocation-model pic -pic-level 2 -mframe-pointer=none -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/home/yano/llvm-project/clang/lib/Driver/ToolChains -v -fcoverage-compilation-dir=/home/yano/llvm-project/clang/lib/Driver/ToolChains -resource-dir /usr/lib/clang/20 -internal-isystem /usr/local/include -internal-isystem /usr/lib/clang/20/include -internal-isystem /usr/bin/../lib/gcc/x86_64-pc-cygwin/12/../../../../x86_64-pc-cygwin/include -internal-externc-isystem /include -internal-externc-isystem /usr/include -internal-externc-isystem /usr/include/w32api -ferror-limit 19 -femulated-tls -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -exception-model=seh -fcolor-diagnostics -faddrsig -o /tmp/--cf4ef2.o -x c -
clang -cc1 version 20.1.7 based upon LLVM 20.1.7 default target x86_64-pc-windows-cygnus
ignoring nonexistent directory "/usr/bin/../lib/gcc/x86_64-pc-cygwin/12/../../../../x86_64-pc-cygwin/include"
ignoring nonexistent directory "/include"
#include "..." search starts here:
#include <...> search starts here:
 /usr/local/include
 /usr/lib/clang/20/include
 /usr/include
 /usr/include/w32api
End of search list.
 "/usr/bin/ld" -m i386pep --wrap=_Znwm --wrap=_Znam --wrap=_ZdlPv --wrap=_ZdaPv --wrap=_ZnwmRKSt9nothrow_t --wrap=_ZnamRKSt9nothrow_t --wrap=_ZdlPvRKSt9nothrow_t --wrap=_ZdaPvRKSt9nothrow_t -o a.exe /usr/bin/../lib/crt0.o /usr/bin/../lib/gcc/x86_64-pc-cygwin/12/crtbegin.o -L/usr/bin/../lib/gcc/x86_64-pc-cygwin/12 -L/usr/bin/../lib/gcc/x86_64-pc-cygwin/12/../../../../x86_64-pc-cygwin/lib -L/usr/bin/../lib -L/lib -L/usr/lib -L/usr/lib/w32api /tmp/--cf4ef2.o -lgcc -lgcc_s -lc -lcygwin -ladvapi32 -lshell32 -luser32 -lkernel32 -lgcc -lgcc_s /usr/bin/../lib/gcc/x86_64-pc-cygwin/12/crtend.o
/usr/bin/ld: /usr/bin/../lib/libcygwin.a(libcmain.o): in function `main':
/usr/src/debug/cygwin-3.6.3-1/winsup/cygwin/lib/libcmain.c:37:(.text.startup+0x79): undefined reference to `WinMain'
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Does this patch consider to use of LLD? ( gcc and clang can use with LLD: -fuse-ld=lld -L/usr/lib/w32api -Wl,--enable-auto-import,--disable-high-entropy-va,--disable-nxcompat,--wrap,_Znwm,--wrap,_Znam,--wrap,_ZdlPv,--wrap,_ZdaPv,--wrap,_ZnwmRKSt9nothrow_t,--wrap,_ZnamRKSt9nothrow_t,--wrap,_ZdlPvRKSt9nothrow_t,--wrap,_ZdaPvRKSt9nothrow_t,-u,_Znwm,-u,_Znam,-u,_ZdlPv,-u,_ZdaPv,-u,_ZnwmRKSt9nothrow_t,-u,_ZnamRKSt9nothrow_t,-u,_ZdlPvRKSt9nothrow_t,-u,_ZdaPvRKSt9nothrow_t )

I confirmed 'clang++ -fuse-ld=lld` works. What is your concern about LLD?

@kikairoya
Copy link
Contributor

Thanks.

It looks like some linker options might still be missing or need adjustments:

  • --enable-auto-import, --disable-high-entropy-va, and --dll-search-prefix=cyg are generally required on Cygwin.
  • -pie has no effect when targeting PE/COFF and can be ignored.

Also, a few options should be handled in both class Cygwin and in the linker job construction:

  • -mwindows should be translated into --subsystem=windows.
  • -shared and -mdll are subtly different in what they instruct GCC to link with.

I confirmed 'clang++ -fuse-ld=lld` works. What is your concern about LLD?

Here are a few issues I've seen when using LLD:

It would be great if this patch could be adjusted to support bootstrapping without requiring extra linker flags.

@kikairoya
Copy link
Contributor

If we should consider cross-compiling, it's needed to be aware that $SYSROOT/usr/lib doesn't appear in real filesystem.


if (IsShared) {
CmdArgs.push_back("-e");
CmdArgs.push_back(ToolChain.getTriple().isArch32Bit()
Copy link
Contributor

Choose a reason for hiding this comment

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

This should only be for x86, not all 32-bit arches (though that isn't really important for Cygwin, which isn't expected to handle any additional 32-bit arches)

Suggested change
CmdArgs.push_back(ToolChain.getTriple().isArch32Bit()
CmdArgs.push_back(ToolChain.getArch() == llvm::Triple::x86

Based on what MinGW.cpp does

Copy link
Member

Choose a reason for hiding this comment

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

Yes, even if other 32 bit architectures may be irrelevant, keep i386 specific things like symbol decoration/prefixes guarded with such a precise arch check.

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.

5 participants