mirror of
https://github.com/ldc-developers/ldc.git
synced 2025-04-28 14:10:42 +03:00

This is achieved by linking the dynamic variant of phobos against zlib and having the compiler add -lz when linking an application that embeds the static variant of phobos. Signed-off-by: Andrei Horodniceanu <a.horodniceanu@proton.me>
847 lines
27 KiB
C++
847 lines
27 KiB
C++
//===-- linker-gcc.cpp ----------------------------------------------------===//
|
|
//
|
|
// LDC - the LLVM D compiler
|
|
//
|
|
// This file is distributed under the BSD-style LDC license. See the LICENSE
|
|
// file for details.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "dmd/errors.h"
|
|
#include "driver/cl_options.h"
|
|
#include "driver/cl_options_instrumentation.h"
|
|
#include "driver/cl_options_sanitizers.h"
|
|
#include "driver/configfile.h"
|
|
#include "driver/exe_path.h"
|
|
#include "driver/ldc-version.h"
|
|
#include "driver/linker.h"
|
|
#include "driver/tool.h"
|
|
#include "gen/irstate.h"
|
|
#include "gen/logger.h"
|
|
#include "gen/optimizer.h"
|
|
#include "llvm/ProfileData/InstrProf.h"
|
|
#include "llvm/Support/FileSystem.h"
|
|
#include "llvm/Support/Path.h"
|
|
#include "llvm/Target/TargetMachine.h"
|
|
#include "llvm/Target/TargetOptions.h"
|
|
|
|
#if LDC_WITH_LLD
|
|
#include "lld/Common/Driver.h"
|
|
#if LDC_LLVM_VER >= 1700
|
|
LLD_HAS_DRIVER(coff)
|
|
LLD_HAS_DRIVER(elf)
|
|
LLD_HAS_DRIVER(mingw)
|
|
LLD_HAS_DRIVER(macho)
|
|
LLD_HAS_DRIVER(wasm)
|
|
#endif
|
|
#endif
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
static llvm::cl::opt<std::string>
|
|
ltoLibrary("flto-binary", llvm::cl::ZeroOrMore,
|
|
llvm::cl::desc("Set the linker LTO plugin library file (e.g. "
|
|
"LLVMgold.so (Unixes) or libLTO.dylib (Darwin))"),
|
|
llvm::cl::value_desc("file"));
|
|
|
|
static llvm::cl::opt<bool> linkNoCpp(
|
|
"link-no-cpp", llvm::cl::ZeroOrMore, llvm::cl::Hidden,
|
|
llvm::cl::desc("Disable automatic linking with the C++ standard library."));
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace {
|
|
|
|
class ArgsBuilder {
|
|
public:
|
|
std::vector<std::string> args;
|
|
|
|
virtual ~ArgsBuilder() = default;
|
|
|
|
void build(llvm::StringRef outputPath,
|
|
const std::vector<std::string> &defaultLibNames);
|
|
|
|
private:
|
|
virtual void addSanitizers(const llvm::Triple &triple);
|
|
virtual void addSanitizerLinkFlags(const llvm::Triple &triple,
|
|
const llvm::StringRef sanitizerName,
|
|
const llvm::StringRef fallbackFlag);
|
|
virtual void addFuzzLinkFlags(const llvm::Triple &triple);
|
|
virtual void addCppStdlibLinkFlags(const llvm::Triple &triple);
|
|
virtual void addProfileRuntimeLinkFlags(const llvm::Triple &triple);
|
|
virtual void addXRayLinkFlags(const llvm::Triple &triple);
|
|
virtual bool addCompilerRTArchiveLinkFlags(llvm::StringRef baseName,
|
|
const llvm::Triple &triple);
|
|
|
|
virtual void addLinker();
|
|
virtual void addUserSwitches();
|
|
void addDefaultPlatformLibs();
|
|
virtual void addTargetFlags();
|
|
|
|
void addLTOGoldPluginFlags(bool requirePlugin);
|
|
void addDarwinLTOFlags();
|
|
void addLTOLinkFlags();
|
|
bool isLldDefaultLinker();
|
|
|
|
virtual void addLdFlag(const llvm::Twine &flag) {
|
|
args.push_back(("-Wl," + flag).str());
|
|
}
|
|
|
|
virtual void addLdFlag(const llvm::Twine &flag1, const llvm::Twine &flag2) {
|
|
args.push_back(("-Wl," + flag1 + "," + flag2).str());
|
|
}
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// LTO functionality
|
|
|
|
std::string getLTOGoldPluginPath() {
|
|
if (!ltoLibrary.empty()) {
|
|
if (llvm::sys::fs::exists(ltoLibrary))
|
|
return ltoLibrary;
|
|
|
|
error(Loc(), "-flto-binary: file '%s' not found", ltoLibrary.c_str());
|
|
fatal();
|
|
} else {
|
|
std::string searchPaths[] = {
|
|
// The plugin packaged with LDC has a "-ldc" suffix.
|
|
exe_path::prependLibDir("LLVMgold-ldc.so"),
|
|
// Perhaps the user copied the plugin to LDC's lib dir.
|
|
exe_path::prependLibDir("LLVMgold.so"),
|
|
#ifdef LDC_LLVM_LIBDIR
|
|
LDC_LLVM_LIBDIR "/LLVMgold.so",
|
|
#endif
|
|
#if __LP64__
|
|
"/usr/local/lib64/LLVMgold.so",
|
|
#endif
|
|
"/usr/local/lib/LLVMgold.so",
|
|
#if __LP64__
|
|
"/usr/lib64/LLVMgold.so",
|
|
#endif
|
|
"/usr/lib/LLVMgold.so",
|
|
"/usr/lib/bfd-plugins/LLVMgold.so",
|
|
};
|
|
|
|
// Try all searchPaths and early return upon the first path found.
|
|
for (const auto &p : searchPaths) {
|
|
if (llvm::sys::fs::exists(p))
|
|
return p;
|
|
}
|
|
|
|
error(Loc(), "The LLVMgold.so plugin (needed for LTO) was not found. You "
|
|
"can specify its path with -flto-binary=<file>.");
|
|
fatal();
|
|
}
|
|
}
|
|
|
|
void ArgsBuilder::addLTOGoldPluginFlags(bool requirePlugin) {
|
|
if (requirePlugin)
|
|
addLdFlag("-plugin", getLTOGoldPluginPath());
|
|
|
|
if (opts::isUsingThinLTO())
|
|
addLdFlag("-plugin-opt=thinlto");
|
|
|
|
const auto cpu = gTargetMachine->getTargetCPU();
|
|
if (!cpu.empty())
|
|
addLdFlag(llvm::Twine("-plugin-opt=mcpu=") + cpu);
|
|
|
|
// Use the O-level passed to LDC as the O-level for LTO, but restrict it to
|
|
// the [0, 3] range that can be passed to the linker plugin.
|
|
static char optChars[15] = "-plugin-opt=O0";
|
|
optChars[13] = '0' + std::min<char>(optLevel(), 3);
|
|
addLdFlag(optChars);
|
|
|
|
const llvm::TargetOptions &TO = gTargetMachine->Options;
|
|
if (TO.FunctionSections)
|
|
addLdFlag("-plugin-opt=-function-sections");
|
|
if (TO.DataSections)
|
|
addLdFlag("-plugin-opt=-data-sections");
|
|
|
|
#if LDC_LLVM_VER >= 1600 && LDC_LLVM_VER < 1700
|
|
// LLVM 16: disable function specializations by default
|
|
addLdFlag("-plugin-opt=-func-specialization-size-threshold=1000000000");
|
|
#endif
|
|
}
|
|
|
|
// Returns an empty string when libLTO.dylib was not specified nor found.
|
|
std::string getLTOdylibPath() {
|
|
if (!ltoLibrary.empty()) {
|
|
if (llvm::sys::fs::exists(ltoLibrary))
|
|
return ltoLibrary;
|
|
|
|
error(Loc(), "-flto-binary: '%s' not found", ltoLibrary.c_str());
|
|
fatal();
|
|
} else {
|
|
// Give priority to the plugin packaged with LDC.
|
|
std::string searchPath = exe_path::prependLibDir("libLTO.dylib");
|
|
if (llvm::sys::fs::exists(searchPath))
|
|
return searchPath;
|
|
|
|
#ifdef LDC_LLVM_LIBDIR
|
|
searchPath = LDC_LLVM_LIBDIR "/libLTO.dylib";
|
|
if (llvm::sys::fs::exists(searchPath))
|
|
return searchPath;
|
|
#endif
|
|
|
|
return "";
|
|
}
|
|
}
|
|
|
|
void ArgsBuilder::addDarwinLTOFlags() {
|
|
std::string dylibPath = getLTOdylibPath();
|
|
if (!dylibPath.empty()) {
|
|
addLdFlag("-lto_library", dylibPath);
|
|
|
|
#if LDC_LLVM_VER >= 1600 && LDC_LLVM_VER < 1700
|
|
// LLVM 16: disable function specializations by default
|
|
addLdFlag("-mllvm", "-func-specialization-size-threshold=1000000000");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/// Adds the required linker flags for LTO builds to args.
|
|
void ArgsBuilder::addLTOLinkFlags() {
|
|
bool isLld = opts::linker == "lld" || useInternalLLDForLinking() ||
|
|
(opts::linker.empty() && isLldDefaultLinker());
|
|
if (global.params.targetTriple->isOSLinux() ||
|
|
global.params.targetTriple->isOSFreeBSD() ||
|
|
global.params.targetTriple->isOSNetBSD() ||
|
|
global.params.targetTriple->isOSOpenBSD() ||
|
|
global.params.targetTriple->isOSDragonFly()) {
|
|
// LLD supports LLVM LTO natively, do not add the plugin itself.
|
|
// Otherwise, assume that ld.gold or ld.bfd is used with plugin support.
|
|
addLTOGoldPluginFlags(!isLld);
|
|
} else if (global.params.targetTriple->isOSDarwin()) {
|
|
addDarwinLTOFlags();
|
|
}
|
|
|
|
// Pass Fat LTO option to LLD
|
|
if (isLld && opts::ltoFatObjects) {
|
|
addLdFlag("--fat-lto-objects");
|
|
}
|
|
}
|
|
|
|
bool ArgsBuilder::isLldDefaultLinker() {
|
|
auto triple = global.params.targetTriple;
|
|
if (triple->isOSFreeBSD()) {
|
|
if (triple->getOSMajorVersion() >= 12 &&
|
|
triple->getArch() == llvm::Triple::ArchType::x86_64)
|
|
return true;
|
|
if (triple->getArch() == llvm::Triple::ArchType::aarch64)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Returns the arch name as used in the compiler_rt libs.
|
|
// FIXME: implement correctly for non-x86 platforms (e.g. ARM)
|
|
// See clang/lib/Driver/Toolchain.cpp.
|
|
llvm::StringRef getCompilerRTArchName(const llvm::Triple &triple) {
|
|
return triple.getArchName();
|
|
}
|
|
|
|
// Appends arch suffix and extension.
|
|
// E.g., for name="libclang_rt.fuzzer" and sharedLibrary=false, returns
|
|
// "libclang_rt.fuzzer_osx.a" on Darwin.
|
|
std::string getCompilerRTLibFilename(const llvm::Twine &name,
|
|
const llvm::Triple &triple,
|
|
bool sharedLibrary) {
|
|
return (triple.isOSDarwin()
|
|
? name + (sharedLibrary ? "_osx_dynamic.dylib" : "_osx.a")
|
|
: name + "-" + getCompilerRTArchName(triple) +
|
|
(sharedLibrary ? ".so" : ".a"))
|
|
.str();
|
|
}
|
|
|
|
// Clang's RT libs are in a subdir of the lib dir.
|
|
// E.g., for name="libclang_rt.asan" and sharedLibrary=true, returns
|
|
// "clang/6.0.0/lib/darwin/libclang_rt.asan_osx_dynamic.dylib" and
|
|
// "clang/6/lib/darwin/libclang_rt.asan_osx_dynamic.dylib" on
|
|
// Darwin.
|
|
// This function is "best effort", the path may not be what Clang does...
|
|
// See clang/lib/Driver/Toolchain.cpp.
|
|
std::vector<std::string> getRelativeClangCompilerRTLibPath(
|
|
const llvm::Twine &name, const llvm::Triple &triple, bool sharedLibrary) {
|
|
llvm::StringRef OSName = triple.isOSDarwin() ? "darwin"
|
|
: triple.isOSFreeBSD() ? "freebsd"
|
|
: triple.getOSName();
|
|
|
|
auto llvm_major_version =
|
|
llvm::StringRef(ldc::llvm_version_base).take_until([](char c) {
|
|
return c == '.';
|
|
});
|
|
|
|
std::string relPath = (llvm::Twine("clang/") + ldc::llvm_version_base +
|
|
"/lib/" + OSName + "/" + name)
|
|
.str();
|
|
std::string relPath_llvm_major_version =
|
|
(llvm::Twine("clang/") + llvm_major_version + "/lib/" + OSName + "/" +
|
|
name)
|
|
.str();
|
|
|
|
return {getCompilerRTLibFilename(relPath, triple, sharedLibrary),
|
|
getCompilerRTLibFilename(relPath_llvm_major_version, triple,
|
|
sharedLibrary)};
|
|
}
|
|
|
|
void appendFullLibPathCandidates(std::vector<std::string> &paths,
|
|
const llvm::Twine &filename) {
|
|
llvm::SmallString<128> candidate;
|
|
for (const char *dir : ConfigFile::instance.libDirs()) {
|
|
candidate = dir;
|
|
llvm::sys::path::append(candidate, filename);
|
|
paths.emplace_back(candidate.data(), candidate.size());
|
|
}
|
|
|
|
// for backwards compatibility
|
|
paths.push_back(exe_path::prependLibDir(filename));
|
|
|
|
#ifdef LDC_LLVM_LIBDIR
|
|
candidate = LDC_LLVM_LIBDIR;
|
|
llvm::sys::path::append(candidate, filename);
|
|
paths.emplace_back(candidate.data(), candidate.size());
|
|
#endif
|
|
}
|
|
|
|
// Returns candidates of full paths to a compiler-rt lib.
|
|
// E.g., for baseName="asan" and sharedLibrary=false, returns something like
|
|
// [ "<libDir>/libldc_rt.asan.a",
|
|
// "<libDir>/libclang_rt.asan_osx.a",
|
|
// "<libDir>/clang/6.0.0/lib/darwin/libclang_rt.asan_osx.a",
|
|
// "<libDir>/clang/6/lib/darwin/libclang_rt.asan_osx.a" ].
|
|
std::vector<std::string>
|
|
getFullCompilerRTLibPathCandidates(llvm::StringRef baseName,
|
|
const llvm::Triple &triple,
|
|
bool sharedLibrary = false) {
|
|
std::vector<std::string> r;
|
|
const auto ldcRT =
|
|
("libldc_rt." + baseName +
|
|
(!sharedLibrary ? ".a" : triple.isOSDarwin() ? ".dylib" : ".so"))
|
|
.str();
|
|
appendFullLibPathCandidates(r, ldcRT);
|
|
const auto clangRT = getCompilerRTLibFilename("libclang_rt." + baseName,
|
|
triple, sharedLibrary);
|
|
appendFullLibPathCandidates(r, clangRT);
|
|
const auto fullClangRT = getRelativeClangCompilerRTLibPath(
|
|
"libclang_rt." + baseName, triple, sharedLibrary);
|
|
for (const auto &path : fullClangRT) {
|
|
appendFullLibPathCandidates(r, path);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
void ArgsBuilder::addSanitizerLinkFlags(const llvm::Triple &triple,
|
|
const llvm::StringRef sanitizerName,
|
|
const llvm::StringRef fallbackFlag) {
|
|
// Examples: "libclang_rt.tsan-x86_64.a" or "libclang_rt.tsan-arm.a" and
|
|
// "libclang_rt.tsan-x86_64.so"
|
|
|
|
// TODO: let user choose to link with shared lib.
|
|
// In case of shared ASan, I think we also need to statically link with
|
|
// libclang_rt.asan-preinit-<arch>.a on Linux. On Darwin, the only option is
|
|
// to use the shared library.
|
|
bool linkSharedLibrary = triple.isOSDarwin();
|
|
const auto searchPaths =
|
|
getFullCompilerRTLibPathCandidates(sanitizerName, triple, linkSharedLibrary);
|
|
|
|
for (const auto &filepath : searchPaths) {
|
|
IF_LOG Logger::println("Searching sanitizer lib: %s", filepath.c_str());
|
|
|
|
if (llvm::sys::fs::exists(filepath) &&
|
|
!llvm::sys::fs::is_directory(filepath)) {
|
|
IF_LOG Logger::println("Found, linking with %s", filepath.c_str());
|
|
args.push_back(filepath);
|
|
|
|
if (linkSharedLibrary) {
|
|
// Add @executable_path to rpath to support having the shared lib copied
|
|
// with the executable.
|
|
args.push_back("-rpath");
|
|
args.push_back("@executable_path");
|
|
|
|
// Add the path to the resource dir to rpath to support using the shared
|
|
// lib from the default location without copying.
|
|
args.push_back("-rpath");
|
|
args.push_back(std::string(llvm::sys::path::parent_path(filepath)));
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// When we reach here, we did not find the sanitizer library.
|
|
// Fallback, requires Clang.
|
|
args.emplace_back(fallbackFlag);
|
|
}
|
|
|
|
// Adds all required link flags for -fsanitize=fuzzer when libFuzzer library is
|
|
// found.
|
|
void ArgsBuilder::addFuzzLinkFlags(const llvm::Triple &triple) {
|
|
const auto searchPaths = getFullCompilerRTLibPathCandidates("fuzzer", triple);
|
|
|
|
for (const auto &filepath : searchPaths) {
|
|
IF_LOG Logger::println("Searching libFuzzer: %s", filepath.c_str());
|
|
|
|
if (llvm::sys::fs::exists(filepath) &&
|
|
!llvm::sys::fs::is_directory(filepath)) {
|
|
IF_LOG Logger::println("Found, linking with %s", filepath.c_str());
|
|
args.push_back(filepath);
|
|
|
|
// libFuzzer requires the C++ std library, but only add the link flags
|
|
// when libFuzzer was found.
|
|
addCppStdlibLinkFlags(triple);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Adds all required link flags for -fxray-instrument when the xray library is
|
|
// found.
|
|
void ArgsBuilder::addXRayLinkFlags(const llvm::Triple &triple) {
|
|
if (!triple.isOSLinux())
|
|
warning(Loc(), "XRay may not be fully supported on non-Linux target OS.");
|
|
|
|
bool libraryFoundAndLinked = addCompilerRTArchiveLinkFlags("xray", triple);
|
|
if (libraryFoundAndLinked) {
|
|
addCompilerRTArchiveLinkFlags("xray-basic", triple);
|
|
addCompilerRTArchiveLinkFlags("xray-fdr", triple);
|
|
}
|
|
}
|
|
|
|
// Returns true if library was found and added to link flags.
|
|
bool ArgsBuilder::addCompilerRTArchiveLinkFlags(llvm::StringRef baseName,
|
|
const llvm::Triple &triple) {
|
|
const bool linkerDarwin = triple.isOSDarwin();
|
|
const auto searchPaths = getFullCompilerRTLibPathCandidates(baseName, triple);
|
|
for (const auto &filepath : searchPaths) {
|
|
IF_LOG Logger::println("Searching runtime library: %s", filepath.c_str());
|
|
|
|
if (llvm::sys::fs::exists(filepath) &&
|
|
!llvm::sys::fs::is_directory(filepath)) {
|
|
if (!linkerDarwin)
|
|
addLdFlag("--whole-archive");
|
|
|
|
IF_LOG Logger::println("Found, linking with %s", filepath.c_str());
|
|
args.push_back(filepath);
|
|
|
|
if (!linkerDarwin)
|
|
addLdFlag("--no-whole-archive");
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void ArgsBuilder::addCppStdlibLinkFlags(const llvm::Triple &triple) {
|
|
if (linkNoCpp)
|
|
return;
|
|
|
|
switch (triple.getOS()) {
|
|
case llvm::Triple::Linux:
|
|
if (triple.getEnvironment() == llvm::Triple::Android) {
|
|
args.push_back("-lc++");
|
|
} else {
|
|
args.push_back("-lstdc++");
|
|
}
|
|
break;
|
|
case llvm::Triple::Solaris:
|
|
case llvm::Triple::NetBSD:
|
|
case llvm::Triple::OpenBSD:
|
|
case llvm::Triple::DragonFly:
|
|
args.push_back("-lstdc++");
|
|
break;
|
|
case llvm::Triple::Darwin:
|
|
case llvm::Triple::MacOSX:
|
|
case llvm::Triple::IOS:
|
|
case llvm::Triple::WatchOS:
|
|
case llvm::Triple::TvOS:
|
|
case llvm::Triple::FreeBSD:
|
|
args.push_back("-lc++");
|
|
break;
|
|
default:
|
|
// Don't know: do nothing so the user can step in
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Adds all required link flags for PGO.
|
|
void ArgsBuilder::addProfileRuntimeLinkFlags(const llvm::Triple &triple) {
|
|
const auto searchPaths =
|
|
getFullCompilerRTLibPathCandidates("profile", triple);
|
|
|
|
if (global.params.targetTriple->isOSLinux()) {
|
|
// For Linux, explicitly define __llvm_profile_runtime as undefined
|
|
// symbol, so that the initialization part of profile-rt is linked in.
|
|
addLdFlag("-u", llvm::getInstrProfRuntimeHookVarName());
|
|
}
|
|
|
|
for (const auto &filepath : searchPaths) {
|
|
IF_LOG Logger::println("Searching profile runtime: %s", filepath.c_str());
|
|
|
|
if (llvm::sys::fs::exists(filepath)) {
|
|
IF_LOG Logger::println("Found, linking with %s", filepath.c_str());
|
|
args.push_back(filepath);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ArgsBuilder::addSanitizers(const llvm::Triple &triple) {
|
|
if (opts::isSanitizerEnabled(opts::AddressSanitizer)) {
|
|
addSanitizerLinkFlags(triple, "asan", "-fsanitize=address");
|
|
} else if (opts::isSanitizerEnabled(opts::LeakSanitizer)) {
|
|
// If ASan is enabled, it includes LSan. So only add LSan link flags if ASan is _not_ enabled already.
|
|
addSanitizerLinkFlags(triple, "lsan", "-fsanitize=leak");
|
|
}
|
|
|
|
if (opts::isSanitizerEnabled(opts::FuzzSanitizer)) {
|
|
addFuzzLinkFlags(triple);
|
|
}
|
|
|
|
if (opts::isSanitizerEnabled(opts::MemorySanitizer)) {
|
|
addSanitizerLinkFlags(triple, "msan", "-fsanitize=memory");
|
|
}
|
|
|
|
if (opts::isSanitizerEnabled(opts::ThreadSanitizer)) {
|
|
addSanitizerLinkFlags(triple, "tsan", "-fsanitize=thread");
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
void ArgsBuilder::build(llvm::StringRef outputPath,
|
|
const std::vector<std::string> &defaultLibNames) {
|
|
const auto &triple = *global.params.targetTriple;
|
|
|
|
// object files
|
|
for (auto objfile : global.params.objfiles) {
|
|
args.push_back(objfile);
|
|
}
|
|
|
|
// add precompiled rt.dso object file (in lib directory) when linking
|
|
// against shared druntime
|
|
const auto &libDirs = ConfigFile::instance.libDirs();
|
|
if (!defaultLibNames.empty() && linkAgainstSharedDefaultLibs() &&
|
|
!libDirs.empty()) {
|
|
args.push_back((llvm::Twine(libDirs[0]) + "/ldc_rt.dso.o").str());
|
|
}
|
|
|
|
// Link with profile-rt library when generating an instrumented binary.
|
|
if (opts::isInstrumentingForPGO()) {
|
|
addProfileRuntimeLinkFlags(triple);
|
|
}
|
|
|
|
if (opts::enableDynamicCompile) {
|
|
args.push_back("-lldc-jit-rt");
|
|
args.push_back("-lldc-jit");
|
|
}
|
|
|
|
// user libs
|
|
for (auto libfile : global.params.libfiles) {
|
|
args.push_back(libfile);
|
|
}
|
|
for (auto dllfile : global.params.dllfiles) {
|
|
args.push_back(dllfile);
|
|
}
|
|
|
|
if (global.params.dll) {
|
|
args.push_back("-shared");
|
|
}
|
|
|
|
if (linkFullyStatic() == llvm::cl::BOU_TRUE) {
|
|
args.push_back("-static");
|
|
}
|
|
|
|
args.push_back("-o");
|
|
args.push_back(std::string(outputPath));
|
|
|
|
addSanitizers(triple);
|
|
|
|
if (opts::fXRayInstrument) {
|
|
addXRayLinkFlags(triple);
|
|
}
|
|
|
|
// Add LTO link flags before adding the user link switches, such that the user
|
|
// can pass additional options to the LTO plugin.
|
|
if (opts::isUsingLTO())
|
|
addLTOLinkFlags();
|
|
|
|
addLinker();
|
|
addUserSwitches();
|
|
|
|
// lib dirs
|
|
for (const char *dir_c : libDirs) {
|
|
const llvm::StringRef dir(dir_c);
|
|
if (!dir.empty())
|
|
args.push_back(("-L" + dir).str());
|
|
}
|
|
|
|
// default libs
|
|
for (const auto &name : defaultLibNames) {
|
|
args.push_back("-l" + name);
|
|
}
|
|
#ifdef PHOBOS_SYSTEM_ZLIB
|
|
if (!defaultLibNames.empty() && !linkAgainstSharedDefaultLibs())
|
|
args.push_back("-lz");
|
|
#endif
|
|
|
|
// libs added via pragma(lib, libname)
|
|
for (auto ls : global.params.linkswitches) {
|
|
args.push_back(ls);
|
|
}
|
|
|
|
// -rpath if linking against shared default libs or ldc-jit
|
|
if (linkAgainstSharedDefaultLibs() || opts::enableDynamicCompile) {
|
|
llvm::StringRef rpath = ConfigFile::instance.rpath();
|
|
if (!rpath.empty())
|
|
addLdFlag("-rpath", rpath);
|
|
}
|
|
|
|
// Make sure we don't do --gc-sections when generating a profile-
|
|
// instrumented binary. The runtime relies on magic sections, which
|
|
// would be stripped by gc-section on older version of ld, see bug:
|
|
// https://sourceware.org/bugzilla/show_bug.cgi?id=19161
|
|
if (!opts::disableLinkerStripDead && !opts::isInstrumentingForPGO()) {
|
|
if (triple.isOSBinFormatMachO()) {
|
|
addLdFlag("-dead_strip");
|
|
} else {
|
|
addLdFlag("--gc-sections");
|
|
}
|
|
}
|
|
|
|
const auto explicitPlatformLibs = getExplicitPlatformLibs();
|
|
#if LDC_LLVM_VER >= 1600
|
|
if (explicitPlatformLibs.has_value()) {
|
|
for (const auto &name : explicitPlatformLibs.value()) {
|
|
args.push_back("-l" + name);
|
|
}
|
|
}
|
|
#else
|
|
if (explicitPlatformLibs.hasValue()) {
|
|
for (const auto &name : explicitPlatformLibs.getValue()) {
|
|
args.push_back("-l" + name);
|
|
}
|
|
}
|
|
#endif
|
|
else {
|
|
addDefaultPlatformLibs();
|
|
}
|
|
|
|
addTargetFlags();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
void ArgsBuilder::addLinker() {
|
|
llvm::StringRef linker = opts::linker;
|
|
|
|
if (!linker.empty())
|
|
args.push_back(("-fuse-ld=" + linker).str());
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
void ArgsBuilder::addUserSwitches() {
|
|
#if LDC_LLVM_VER >= 1800
|
|
#define startswith starts_with
|
|
#endif
|
|
|
|
// additional linker and cc switches (preserve order across both lists)
|
|
for (unsigned ilink = 0, icc = 0;;) {
|
|
unsigned linkpos = ilink < opts::linkerSwitches.size()
|
|
? opts::linkerSwitches.getPosition(ilink)
|
|
: std::numeric_limits<unsigned>::max();
|
|
unsigned ccpos = icc < opts::ccSwitches.size()
|
|
? opts::ccSwitches.getPosition(icc)
|
|
: std::numeric_limits<unsigned>::max();
|
|
if (linkpos < ccpos) {
|
|
const std::string &p = opts::linkerSwitches[ilink++];
|
|
// Don't push -l and -L switches using -Xlinker, but pass them indirectly
|
|
// via GCC. This makes sure user-defined paths take precedence over
|
|
// GCC's builtin LIBRARY_PATHs.
|
|
// Options starting with `-Wl,`, -shared or -static are not handled by
|
|
// the linker and must be passed to the driver.
|
|
auto str = llvm::StringRef(p);
|
|
if (!(str.startswith("-l") || str.startswith("-L") ||
|
|
str.startswith("-Wl,") || str.startswith("-shared") ||
|
|
str.startswith("-static"))) {
|
|
args.push_back("-Xlinker");
|
|
}
|
|
args.push_back(p);
|
|
} else if (ccpos < linkpos) {
|
|
args.push_back(opts::ccSwitches[icc++]);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if LDC_LLVM_VER >= 1800
|
|
#undef startswith
|
|
#endif
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
void ArgsBuilder::addDefaultPlatformLibs() {
|
|
bool addSoname = false;
|
|
|
|
const auto &triple = *global.params.targetTriple;
|
|
|
|
switch (triple.getOS()) {
|
|
case llvm::Triple::Linux:
|
|
addSoname = true;
|
|
if (triple.getEnvironment() == llvm::Triple::Android) {
|
|
args.push_back("-ldl");
|
|
args.push_back("-lm");
|
|
break;
|
|
}
|
|
args.push_back("-lrt");
|
|
args.push_back("-ldl");
|
|
// fallthrough
|
|
case llvm::Triple::Darwin:
|
|
case llvm::Triple::MacOSX:
|
|
case llvm::Triple::FreeBSD:
|
|
case llvm::Triple::NetBSD:
|
|
case llvm::Triple::OpenBSD:
|
|
case llvm::Triple::DragonFly:
|
|
addSoname = true;
|
|
args.push_back("-lpthread");
|
|
args.push_back("-lm");
|
|
break;
|
|
|
|
case llvm::Triple::Solaris:
|
|
args.push_back("-lm");
|
|
args.push_back("-lumem");
|
|
args.push_back("-lsocket");
|
|
args.push_back("-lnsl");
|
|
break;
|
|
|
|
default:
|
|
// OS not yet handled, will probably lead to linker errors.
|
|
// FIXME: Win32.
|
|
break;
|
|
}
|
|
|
|
if (triple.isWindowsGNUEnvironment()) {
|
|
// This is really more of a kludge, as linking in the Winsock functions
|
|
// should be handled by the pragma(lib, ...) in std.socket, but it
|
|
// makes LDC behave as expected for now.
|
|
args.push_back("-lws2_32");
|
|
}
|
|
|
|
if (global.params.dll && addSoname && !opts::soname.empty()) {
|
|
addLdFlag("-soname", opts::soname);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
void ArgsBuilder::addTargetFlags() { appendTargetArgsForGcc(args); }
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Specialization for plain ld.
|
|
|
|
class LdArgsBuilder : public ArgsBuilder {
|
|
void addSanitizers(const llvm::Triple &triple) override {}
|
|
|
|
void addLinker() override {}
|
|
|
|
void addUserSwitches() override {
|
|
if (!opts::ccSwitches.empty()) {
|
|
warning(Loc(), "Ignoring -Xcc options");
|
|
}
|
|
|
|
args.insert(args.end(), opts::linkerSwitches.begin(),
|
|
opts::linkerSwitches.end());
|
|
}
|
|
|
|
void addTargetFlags() override {}
|
|
|
|
void addLdFlag(const llvm::Twine &flag) override {
|
|
args.push_back(flag.str());
|
|
}
|
|
|
|
void addLdFlag(const llvm::Twine &flag1, const llvm::Twine &flag2) override {
|
|
args.push_back(flag1.str());
|
|
args.push_back(flag2.str());
|
|
}
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
int linkObjToBinaryGcc(llvm::StringRef outputPath,
|
|
const std::vector<std::string> &defaultLibNames) {
|
|
#if LDC_WITH_LLD
|
|
if (useInternalLLDForLinking()) {
|
|
LdArgsBuilder argsBuilder;
|
|
argsBuilder.build(outputPath, defaultLibNames);
|
|
|
|
const auto fullArgs =
|
|
getFullArgs("lld", argsBuilder.args, global.params.v.verbose);
|
|
|
|
// CanExitEarly == true means that LLD can and will call `exit()` when
|
|
// errors occur.
|
|
bool CanExitEarly = false;
|
|
|
|
bool success = false;
|
|
if (global.params.targetTriple->isOSBinFormatELF()) {
|
|
success = lld::elf::link(fullArgs, llvm::outs(), llvm::errs(),
|
|
CanExitEarly, false);
|
|
} else if (global.params.targetTriple->isOSBinFormatMachO()) {
|
|
success = lld::macho::link(fullArgs, llvm::outs(), llvm::errs(),
|
|
CanExitEarly, false);
|
|
} else if (global.params.targetTriple->isOSBinFormatCOFF()) {
|
|
success = lld::mingw::link(fullArgs, llvm::outs(), llvm::errs(),
|
|
CanExitEarly, false);
|
|
} else if (global.params.targetTriple->isOSBinFormatWasm()) {
|
|
#if __linux__
|
|
// FIXME: segfault in cleanup (`freeArena()`) after successful linking,
|
|
// but only on Linux?
|
|
CanExitEarly = true;
|
|
#endif
|
|
success = lld::wasm::link(fullArgs, llvm::outs(), llvm::errs(),
|
|
CanExitEarly, false);
|
|
} else {
|
|
error(Loc(), "unknown target binary format for internal linking");
|
|
}
|
|
|
|
if (!success)
|
|
error(Loc(), "linking with LLD failed");
|
|
|
|
return success ? 0 : 1;
|
|
}
|
|
#endif
|
|
|
|
// build command-line for gcc-compatible linker driver
|
|
// exception: invoke (ld-compatible) linker directly for WebAssembly targets
|
|
std::string tool;
|
|
std::unique_ptr<ArgsBuilder> argsBuilder;
|
|
if (global.params.targetTriple->isOSBinFormatWasm()) {
|
|
argsBuilder = std::make_unique<LdArgsBuilder>();
|
|
tool = getProgram("wasm-ld", &opts::linker);
|
|
} else {
|
|
argsBuilder = std::make_unique<ArgsBuilder>();
|
|
tool = getGcc(argsBuilder->args);
|
|
}
|
|
|
|
// build arguments
|
|
argsBuilder->build(outputPath, defaultLibNames);
|
|
|
|
Logger::println("Linking with: ");
|
|
Stream logstr = Logger::cout();
|
|
for (const auto &arg : argsBuilder->args) {
|
|
if (!arg.empty()) {
|
|
logstr << "'" << arg << "' ";
|
|
}
|
|
}
|
|
logstr << "\n"; // FIXME where's flush ?
|
|
|
|
// try to call linker
|
|
return executeToolAndWait(Loc(), tool, argsBuilder->args,
|
|
global.params.v.verbose);
|
|
}
|