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
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions clang/lib/Driver/ToolChain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
289 changes: 289 additions & 0 deletions clang/lib/Driver/ToolChains/Cygwin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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);

Expand Down Expand Up @@ -107,3 +110,289 @@ void Cygwin::AddClangSystemIncludeArgs(const ArgList &DriverArgs,
addExternCSystemInclude(DriverArgs, CC1Args, SysRoot + "/usr/include");
addExternCSystemInclude(DriverArgs, CC1Args, SysRoot + "/usr/include/w32api");
}

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 IsStatic = Args.hasArg(options::OPT_static);

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 (Args.hasArg(options::OPT_marm64x))
CmdArgs.push_back("arm64xpe");
else if (ToolChain.getEffectiveTriple().isWindowsArm64EC())
CmdArgs.push_back("arm64ecpe");
else
CmdArgs.push_back("arm64pe");
break;
case llvm::Triple::mipsel:
CmdArgs.push_back("mipspe");
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");
const char *OutputFile = Output.getFilename();
// GCC implicitly adds an .exe extension if it is given an output file name
// that lacks an extension.
// GCC used to do this only when the compiler itself runs on windows, but
// since GCC 8 it does the same when cross compiling as well.
if (!llvm::sys::path::has_extension(OutputFile)) {
CmdArgs.push_back(Args.MakeArgString(Twine(OutputFile) + ".exe"));
OutputFile = CmdArgs.back();
} else
CmdArgs.push_back(OutputFile);

if (!Args.hasArg(options::OPT_nostdlib, options::OPT_nostartfiles,
options::OPT_r)) {
const bool IsShared = Args.hasArg(options::OPT_shared);
if (Args.hasArg(options::OPT_mdll) || IsShared) {
CmdArgs.push_back("-e");
CmdArgs.push_back(ToolChain.getArch() == llvm::Triple::x86
? "__cygwin_dll_entry@12"
: "_cygwin_dll_entry");
CmdArgs.push_back("--enable-auto-image-base");
}

if (!Args.hasArg(options::OPT_mdll) && !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);

bool saw_high_entropy_va = false;
bool saw_dynamicbase = false;
bool saw_nxcompat = false;
for (const char *Arg : CmdArgs) {
if (StringRef(Arg) == "-high-entropy-va" ||
StringRef(Arg) == "--high-entropy-va" ||
StringRef(Arg) == "-disable-high-entropy-va" ||
StringRef(Arg) == "--disable-high-entropy-va")
saw_high_entropy_va = true;
if (StringRef(Arg) == "-dynamicbase" || StringRef(Arg) == "--dynamicbase" ||
StringRef(Arg) == "-disable-dynamicbase" ||
StringRef(Arg) == "--disable-dynamicbase")
saw_dynamicbase = true;
if (StringRef(Arg) == "-nxcompat" || StringRef(Arg) == "--nxcompat" ||
StringRef(Arg) == "-disable-nxcompat" ||
StringRef(Arg) == "--disable-nxcompat")
saw_dynamicbase = true;
}
if (!saw_high_entropy_va)
CmdArgs.push_back("--disable-high-entropy-va");
if (!saw_dynamicbase)
CmdArgs.push_back("--disable-dynamicbase");
if (!saw_nxcompat)
CmdArgs.push_back("--disable-nxcompat");

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)
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");

if (IsStatic)
CmdArgs.push_back("--end-group");
else
tools::AddRunTimeLibs(ToolChain, D, CmdArgs, Args);
}

if (!Args.hasArg(options::OPT_nostartfiles)) {
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); }
18 changes: 18 additions & 0 deletions clang/lib/Driver/ToolChains/Cygwin.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Empty file.
Loading