[clang] [Clang][Driver] Revise Cygwin ToolChain to call linker directly (PR #147960)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Jul 15 07:18:41 PDT 2025
https://github.com/tyan0 updated https://github.com/llvm/llvm-project/pull/147960
>From 1bb281fa82bbec8ed7759eef5ddf5b5f4991770c Mon Sep 17 00:00:00 2001
From: Takashi Yano <takashi.yano at nifty.ne.jp>
Date: Thu, 10 Jul 2025 21:55:05 +0900
Subject: [PATCH] [Clang][Driver] Revise Cygwin ToolChain to call linker
directly
...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 52924a2d7255.
Signed-off-by: Takashi Yano <takashi.yano at nifty.ne.jp>
---
clang/lib/Driver/ToolChain.cpp | 2 +
clang/lib/Driver/ToolChains/Cygwin.cpp | 286 ++++++++++++++++++
clang/lib/Driver/ToolChains/Cygwin.h | 18 ++
.../usr/lib/gcc/i686-pc-msys/10/crtend.o | 0
.../usr/lib/gcc/x86_64-pc-cygwin/10/crtend.o | 0
.../Inputs/basic_cygwin_tree/usr/lib/crt0.o | 0
.../usr/lib/gcc/i686-pc-cygwin/10/crtend.o | 0
.../usr/lib/gcc/x86_64-pc-msys/10/crtend.o | 0
clang/test/Driver/cygwin.cpp | 16 +-
9 files changed, 314 insertions(+), 8 deletions(-)
create mode 100644 clang/test/Driver/Inputs/basic_cross_cygwin_tree/usr/lib/gcc/i686-pc-msys/10/crtend.o
create mode 100644 clang/test/Driver/Inputs/basic_cross_cygwin_tree/usr/lib/gcc/x86_64-pc-cygwin/10/crtend.o
create mode 100644 clang/test/Driver/Inputs/basic_cygwin_tree/usr/lib/crt0.o
create mode 100644 clang/test/Driver/Inputs/basic_cygwin_tree/usr/lib/gcc/i686-pc-cygwin/10/crtend.o
create mode 100644 clang/test/Driver/Inputs/basic_cygwin_tree/usr/lib/gcc/x86_64-pc-msys/10/crtend.o
diff --git a/clang/lib/Driver/ToolChain.cpp b/clang/lib/Driver/ToolChain.cpp
index 3f9b808b2722e..745275a236ed3 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:
diff --git a/clang/lib/Driver/ToolChains/Cygwin.cpp b/clang/lib/Driver/ToolChains/Cygwin.cpp
index d9c16347daa34..28f895a265456 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,286 @@ 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 = 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();
+ }
+
+ Arg *SubsysArg =
+ Args.getLastArg(options::OPT_mwindows, options::OPT_mconsole);
+ if (SubsysArg && SubsysArg->getOption().matches(options::OPT_mwindows)) {
+ CmdArgs.push_back("--subsystem");
+ CmdArgs.push_back("windows");
+ } else if (SubsysArg &&
+ SubsysArg->getOption().matches(options::OPT_mconsole)) {
+ CmdArgs.push_back("--subsystem");
+ CmdArgs.push_back("console");
+ }
+
+ 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");
+
+ if (Args.hasArg(options::OPT_mdll))
+ CmdArgs.push_back("--dll");
+ else if (Args.hasArg(options::OPT_shared))
+ CmdArgs.push_back("--shared");
+ if (Args.hasArg(options::OPT_static))
+ CmdArgs.push_back("-Bstatic");
+ else
+ CmdArgs.push_back("-Bdynamic");
+
+ CmdArgs.push_back("--dll-search-prefix=cyg");
+
+ 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");
+ }
+
+ const bool IsShared = Args.hasArg(options::OPT_shared);
+ if (IsShared) {
+ CmdArgs.push_back("-e");
+ CmdArgs.push_back(ToolChain.getArch() == llvm::Triple::x86
+ ? "__cygwin_dll_entry at 12"
+ : "_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);
+
+ if (D.isUsingLTO())
+ tools::addLTOOptions(ToolChain, Args, CmdArgs, Output, Inputs,
+ 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)) {
+ ToolChain.addFortranRuntimeLibraryPath(Args, CmdArgs);
+ ToolChain.addFortranRuntimeLibs(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");
+ if (Args.hasArg(options::OPT_mwindows)) {
+ CmdArgs.push_back("-lgdi32");
+ CmdArgs.push_back("-lcomdlg32");
+ }
+ 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..0701d41a63608 100644
--- a/clang/lib/Driver/ToolChains/Cygwin.h
+++ b/clang/lib/Driver/ToolChains/Cygwin.h
@@ -27,7 +27,25 @@ class LLVM_LIBRARY_VISIBILITY Cygwin : public Generic_GCC {
void
AddClangSystemIncludeArgs(const llvm::opt::ArgList &DriverArgs,
llvm::opt::ArgStringList &CC1Args) const override;
+
+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 \
More information about the cfe-commits
mailing list