mirror of
https://github.com/ldc-developers/ldc.git
synced 2025-05-08 03:46:02 +03:00

For invoking MS tools such as link.exe and lib.exe, which require compatible PATH, LIB, LIBPATH etc. We previously wrapped the invokation of the tool with a batch file taking care of setting up the environment and then calling the actual tool. I changed this to first invoke another batch file setting up its environment and then dumping all environment variables to a file. LDC then parses this file and updates its own environment variables. Later spawned child processes then inherit the updated environment, and the tools will be located by LLVM in updated PATH. This allows LDC to keep track of the used Visual C++ installation (and could so set up additional runtime libs required since VS 2015, something we would have needed some time back when we still supported VS 2013). It's also required as a prerequisite for integrating LLD as in-process linker on Windows. I quickly looked into the option of setting up the environment manually, but the MS batch files are full of quirks, mixtures of registry and file-system lookups, checking for existence of misc. headers to filter out bogus installations etc. With VS 2017, there's nothing in the registry at all anymore (at least for the Build Tools); MS expose a COM interface for querying the VS installer... But at least they still provide the batch files.
957 lines
27 KiB
C++
957 lines
27 KiB
C++
//===-- linker.cpp --------------------------------------------------------===//
|
||
//
|
||
// LDC – the LLVM D compiler
|
||
//
|
||
// This file is distributed under the BSD-style LDC license. See the LICENSE
|
||
// file for details.
|
||
//
|
||
//===----------------------------------------------------------------------===//
|
||
|
||
#include "driver/linker.h"
|
||
#include "mars.h"
|
||
#include "module.h"
|
||
#include "root.h"
|
||
#include "driver/archiver.h"
|
||
#include "driver/cl_options.h"
|
||
#include "driver/exe_path.h"
|
||
#include "driver/tool.h"
|
||
#include "gen/irstate.h"
|
||
#include "gen/llvm.h"
|
||
#include "gen/logger.h"
|
||
#include "gen/optimizer.h"
|
||
#include "gen/programs.h"
|
||
#include "llvm/ADT/Triple.h"
|
||
#include "llvm/IRReader/IRReader.h"
|
||
#include "llvm/Linker/Linker.h"
|
||
#include "llvm/ProfileData/InstrProf.h"
|
||
#include "llvm/Support/FileSystem.h"
|
||
#include "llvm/Support/Program.h"
|
||
#include "llvm/Support/Path.h"
|
||
#include "llvm/Support/SourceMgr.h"
|
||
#include "llvm/Target/TargetMachine.h"
|
||
#include "llvm/Target/TargetOptions.h"
|
||
#if _WIN32
|
||
#include "llvm/Support/SystemUtils.h"
|
||
#include "llvm/Support/ConvertUTF.h"
|
||
#include <Windows.h>
|
||
#endif
|
||
|
||
#include <algorithm>
|
||
|
||
//////////////////////////////////////////////////////////////////////////////
|
||
|
||
static llvm::cl::opt<bool> staticFlag(
|
||
"static",
|
||
llvm::cl::desc(
|
||
"Create a statically linked binary, including all system dependencies"),
|
||
llvm::cl::ZeroOrMore);
|
||
|
||
static llvm::cl::opt<std::string>
|
||
ltoLibrary("flto-binary",
|
||
llvm::cl::desc("Set the linker LTO plugin library file (e.g. "
|
||
"LLVMgold.so (Unixes) or libLTO.dylib (Darwin))"),
|
||
llvm::cl::value_desc("file"), llvm::cl::ZeroOrMore);
|
||
|
||
static llvm::cl::opt<std::string>
|
||
externalArchiver("archiver",
|
||
llvm::cl::desc("External static library archiver"),
|
||
llvm::cl::value_desc("file"), llvm::cl::ZeroOrMore);
|
||
|
||
//////////////////////////////////////////////////////////////////////////////
|
||
|
||
static void CreateDirectoryOnDisk(llvm::StringRef fileName) {
|
||
auto dir = llvm::sys::path::parent_path(fileName);
|
||
if (!dir.empty() && !llvm::sys::fs::exists(dir)) {
|
||
if (auto ec = llvm::sys::fs::create_directories(dir)) {
|
||
error(Loc(), "failed to create path to file: %s\n%s", dir.data(),
|
||
ec.message().c_str());
|
||
fatal();
|
||
}
|
||
}
|
||
}
|
||
|
||
//////////////////////////////////////////////////////////////////////////////
|
||
|
||
static std::string getOutputName(bool const sharedLib) {
|
||
const auto &triple = *global.params.targetTriple;
|
||
|
||
const char *extension = nullptr;
|
||
if (sharedLib) {
|
||
extension = global.dll_ext;
|
||
} else if (triple.isOSWindows()) {
|
||
extension = "exe";
|
||
}
|
||
|
||
if (global.params.exefile) {
|
||
// DMD adds the default extension if there is none
|
||
return opts::invokedByLDMD && extension
|
||
? FileName::defaultExt(global.params.exefile, extension)
|
||
: global.params.exefile;
|
||
}
|
||
|
||
// Infer output name from first object file.
|
||
std::string result = global.params.objfiles->dim
|
||
? FileName::removeExt((*global.params.objfiles)[0])
|
||
: "a.out";
|
||
|
||
if (sharedLib && !triple.isWindowsMSVCEnvironment())
|
||
result = "lib" + result;
|
||
|
||
if (global.params.run) {
|
||
// If `-run` is passed, the executable is temporary and is removed
|
||
// after execution. Make sure the name does not collide with other files
|
||
// from other processes by creating a unique filename.
|
||
llvm::SmallString<128> tempFilename;
|
||
auto EC = llvm::sys::fs::createTemporaryFile(FileName::name(result.c_str()),
|
||
extension ? extension : "",
|
||
tempFilename);
|
||
if (!EC)
|
||
result = tempFilename.str();
|
||
} else if (extension) {
|
||
result += '.';
|
||
result += extension;
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
//////////////////////////////////////////////////////////////////////////////
|
||
// LTO functionality
|
||
|
||
namespace {
|
||
|
||
void addLinkerFlag(std::vector<std::string> &args, const llvm::Twine &flag) {
|
||
args.push_back("-Xlinker");
|
||
args.push_back(flag.str());
|
||
}
|
||
|
||
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"),
|
||
#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 addLTOGoldPluginFlags(std::vector<std::string> &args) {
|
||
addLinkerFlag(args, "-plugin");
|
||
addLinkerFlag(args, getLTOGoldPluginPath());
|
||
|
||
if (opts::isUsingThinLTO())
|
||
addLinkerFlag(args, "-plugin-opt=thinlto");
|
||
|
||
if (!opts::mCPU.empty())
|
||
addLinkerFlag(args, llvm::Twine("-plugin-opt=mcpu=") + opts::mCPU);
|
||
|
||
// 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);
|
||
addLinkerFlag(args, optChars);
|
||
|
||
#if LDC_LLVM_VER >= 400
|
||
const llvm::TargetOptions &TO = gTargetMachine->Options;
|
||
if (TO.FunctionSections)
|
||
addLinkerFlag(args, "-plugin-opt=-function-sections");
|
||
if (TO.DataSections)
|
||
addLinkerFlag(args, "-plugin-opt=-data-sections");
|
||
#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 {
|
||
// The plugin packaged with LDC has a "-ldc" suffix.
|
||
std::string searchPath = exe_path::prependLibDir("libLTO-ldc.dylib");
|
||
if (llvm::sys::fs::exists(searchPath))
|
||
return searchPath;
|
||
|
||
return "";
|
||
}
|
||
}
|
||
|
||
void addDarwinLTOFlags(std::vector<std::string> &args) {
|
||
std::string dylibPath = getLTOdylibPath();
|
||
if (!dylibPath.empty()) {
|
||
args.push_back("-lto_library");
|
||
args.push_back(std::move(dylibPath));
|
||
}
|
||
}
|
||
|
||
/// Adds the required linker flags for LTO builds to args.
|
||
void addLTOLinkFlags(std::vector<std::string> &args) {
|
||
#if LDC_LLVM_VER >= 309
|
||
if (global.params.targetTriple->isOSLinux() ||
|
||
global.params.targetTriple->isOSFreeBSD() ||
|
||
global.params.targetTriple->isOSNetBSD() ||
|
||
global.params.targetTriple->isOSOpenBSD() ||
|
||
global.params.targetTriple->isOSDragonFly()) {
|
||
// Assume that ld.gold or ld.bfd is used with plugin support.
|
||
addLTOGoldPluginFlags(args);
|
||
} else if (global.params.targetTriple->isOSDarwin()) {
|
||
addDarwinLTOFlags(args);
|
||
}
|
||
#endif
|
||
}
|
||
} // anonymous namespace
|
||
|
||
//////////////////////////////////////////////////////////////////////////////
|
||
|
||
namespace {
|
||
|
||
#if LDC_LLVM_VER >= 306
|
||
/// Insert an LLVM bitcode file into the module
|
||
void insertBitcodeIntoModule(const char *bcFile, llvm::Module &M,
|
||
llvm::LLVMContext &Context) {
|
||
Logger::println("*** Linking-in bitcode file %s ***", bcFile);
|
||
|
||
llvm::SMDiagnostic Err;
|
||
std::unique_ptr<llvm::Module> loadedModule(
|
||
getLazyIRFileModule(bcFile, Err, Context));
|
||
if (!loadedModule) {
|
||
error(Loc(), "Error when loading LLVM bitcode file: %s", bcFile);
|
||
fatal();
|
||
}
|
||
#if LDC_LLVM_VER >= 308
|
||
llvm::Linker(M).linkInModule(std::move(loadedModule));
|
||
#else
|
||
llvm::Linker(&M).linkInModule(loadedModule.release());
|
||
#endif
|
||
}
|
||
#endif
|
||
}
|
||
|
||
/// Insert LLVM bitcode files into the module
|
||
void insertBitcodeFiles(llvm::Module &M, llvm::LLVMContext &Ctx,
|
||
Array<const char *> &bitcodeFiles) {
|
||
#if LDC_LLVM_VER >= 306
|
||
for (const char *fname : bitcodeFiles) {
|
||
insertBitcodeIntoModule(fname, M, Ctx);
|
||
}
|
||
#else
|
||
if (!bitcodeFiles.empty()) {
|
||
error(Loc(),
|
||
"Passing LLVM bitcode files to LDC is not supported for LLVM < 3.6");
|
||
fatal();
|
||
}
|
||
#endif
|
||
}
|
||
|
||
//////////////////////////////////////////////////////////////////////////////
|
||
|
||
static void appendObjectFiles(std::vector<std::string> &args) {
|
||
for (unsigned i = 0; i < global.params.objfiles->dim; i++)
|
||
args.push_back((*global.params.objfiles)[i]);
|
||
|
||
if (global.params.targetTriple->isWindowsMSVCEnvironment()) {
|
||
if (global.params.resfile)
|
||
args.push_back(global.params.resfile);
|
||
if (global.params.deffile)
|
||
args.push_back(std::string("/DEF:") + global.params.deffile);
|
||
}
|
||
}
|
||
|
||
//////////////////////////////////////////////////////////////////////////////
|
||
|
||
static std::string gExePath;
|
||
|
||
static int linkObjToBinaryGcc(bool sharedLib, bool fullyStatic) {
|
||
Logger::println("*** Linking executable ***");
|
||
|
||
// find gcc for linking
|
||
std::string gcc(getGcc());
|
||
|
||
// build arguments
|
||
std::vector<std::string> args;
|
||
|
||
appendObjectFiles(args);
|
||
|
||
// Link with profile-rt library when generating an instrumented binary.
|
||
// profile-rt uses Phobos (MD5 hashing) and therefore must be passed on the
|
||
// commandline before Phobos.
|
||
if (global.params.genInstrProf) {
|
||
#if LDC_LLVM_VER >= 308
|
||
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.
|
||
args.push_back(
|
||
("-Wl,-u," + llvm::getInstrProfRuntimeHookVarName()).str());
|
||
}
|
||
#endif
|
||
args.push_back("-lldc-profile-rt");
|
||
}
|
||
|
||
// user libs
|
||
for (unsigned i = 0; i < global.params.libfiles->dim; i++)
|
||
args.push_back((*global.params.libfiles)[i]);
|
||
|
||
// output filename
|
||
std::string output = getOutputName(sharedLib);
|
||
|
||
if (sharedLib) {
|
||
args.push_back("-shared");
|
||
}
|
||
|
||
if (fullyStatic) {
|
||
args.push_back("-static");
|
||
}
|
||
|
||
args.push_back("-o");
|
||
args.push_back(output);
|
||
|
||
// set the global gExePath
|
||
gExePath = output;
|
||
// assert(gExePath.isValid());
|
||
|
||
// create path to exe
|
||
CreateDirectoryOnDisk(gExePath);
|
||
|
||
// Pass sanitizer arguments to linker. Requires clang.
|
||
if (opts::sanitize == opts::AddressSanitizer) {
|
||
args.push_back("-fsanitize=address");
|
||
}
|
||
|
||
if (opts::sanitize == opts::MemorySanitizer) {
|
||
args.push_back("-fsanitize=memory");
|
||
}
|
||
|
||
if (opts::sanitize == opts::ThreadSanitizer) {
|
||
args.push_back("-fsanitize=thread");
|
||
}
|
||
|
||
// 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(args);
|
||
|
||
// additional linker switches
|
||
for (unsigned i = 0; i < global.params.linkswitches->dim; i++) {
|
||
const char *p = (*global.params.linkswitches)[i];
|
||
// 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);
|
||
}
|
||
|
||
// default libs
|
||
bool addSoname = false;
|
||
switch (global.params.targetTriple->getOS()) {
|
||
case llvm::Triple::Linux:
|
||
addSoname = true;
|
||
// 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 && !global.params.genInstrProf) {
|
||
args.push_back("-Wl,--gc-sections");
|
||
}
|
||
if (global.params.targetTriple->getEnvironment() == llvm::Triple::Android) {
|
||
args.push_back("-ldl");
|
||
args.push_back("-lm");
|
||
break;
|
||
}
|
||
args.push_back("-lrt");
|
||
// fallthrough
|
||
case llvm::Triple::Darwin:
|
||
case llvm::Triple::MacOSX:
|
||
args.push_back("-ldl");
|
||
// fallthrough
|
||
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 (global.params.targetTriple->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");
|
||
}
|
||
|
||
// Only specify -m32/-m64 for architectures where the two variants actually
|
||
// exist (as e.g. the GCC ARM toolchain doesn't recognize the switches).
|
||
// MIPS does not have -m32/-m64 but requires -mabi=.
|
||
if (global.params.targetTriple->get64BitArchVariant().getArch() !=
|
||
llvm::Triple::UnknownArch &&
|
||
global.params.targetTriple->get32BitArchVariant().getArch() !=
|
||
llvm::Triple::UnknownArch) {
|
||
if (global.params.targetTriple->get64BitArchVariant().getArch() ==
|
||
llvm::Triple::mips64 ||
|
||
global.params.targetTriple->get64BitArchVariant().getArch() ==
|
||
llvm::Triple::mips64el) {
|
||
switch (getMipsABI()) {
|
||
case MipsABI::EABI:
|
||
args.push_back("-mabi=eabi");
|
||
break;
|
||
case MipsABI::O32:
|
||
args.push_back("-mabi=32");
|
||
break;
|
||
case MipsABI::N32:
|
||
args.push_back("-mabi=n32");
|
||
break;
|
||
case MipsABI::N64:
|
||
args.push_back("-mabi=64");
|
||
break;
|
||
case MipsABI::Unknown:
|
||
break;
|
||
}
|
||
} else {
|
||
switch (global.params.targetTriple->getArch()) {
|
||
case llvm::Triple::arm:
|
||
case llvm::Triple::armeb:
|
||
case llvm::Triple::aarch64:
|
||
case llvm::Triple::aarch64_be:
|
||
#if LDC_LLVM_VER == 305
|
||
case llvm::Triple::arm64:
|
||
case llvm::Triple::arm64_be:
|
||
#endif
|
||
break;
|
||
default:
|
||
if (global.params.is64bit) {
|
||
args.push_back("-m64");
|
||
} else {
|
||
args.push_back("-m32");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (global.params.dll && addSoname) {
|
||
std::string soname = opts::soname;
|
||
if (!soname.empty()) {
|
||
args.push_back("-Wl,-soname," + soname);
|
||
}
|
||
}
|
||
|
||
Logger::println("Linking with: ");
|
||
Stream logstr = Logger::cout();
|
||
for (const auto &arg : args) {
|
||
if (!arg.empty()) {
|
||
logstr << "'" << arg << "'"
|
||
<< " ";
|
||
}
|
||
}
|
||
logstr << "\n"; // FIXME where's flush ?
|
||
|
||
// try to call linker
|
||
return executeToolAndWait(gcc, args, global.params.verbose);
|
||
}
|
||
|
||
//////////////////////////////////////////////////////////////////////////////
|
||
|
||
#ifdef _WIN32
|
||
|
||
namespace windows {
|
||
|
||
bool needsQuotes(const llvm::StringRef &arg) {
|
||
return // not already quoted
|
||
!(arg.size() > 1 && arg[0] == '"' &&
|
||
arg.back() == '"') && // empty or min 1 space or min 1 double quote
|
||
(arg.empty() || arg.find(' ') != arg.npos || arg.find('"') != arg.npos);
|
||
}
|
||
|
||
size_t countPrecedingBackslashes(llvm::StringRef arg, size_t index) {
|
||
size_t count = 0;
|
||
|
||
for (size_t i = index - 1; i >= 0; --i) {
|
||
if (arg[i] != '\\')
|
||
break;
|
||
++count;
|
||
}
|
||
|
||
return count;
|
||
}
|
||
|
||
std::string quoteArg(llvm::StringRef arg) {
|
||
if (!needsQuotes(arg))
|
||
return arg;
|
||
|
||
std::string quotedArg;
|
||
quotedArg.reserve(3 + 2 * arg.size()); // worst case
|
||
|
||
quotedArg.push_back('"');
|
||
|
||
const size_t argLength = arg.size();
|
||
for (size_t i = 0; i < argLength; ++i) {
|
||
if (arg[i] == '"') {
|
||
// Escape all preceding backslashes (if any).
|
||
// Note that we *don't* need to escape runs of backslashes that don't
|
||
// precede a double quote! See MSDN:
|
||
// http://msdn.microsoft.com/en-us/library/17w5ykft%28v=vs.85%29.aspx
|
||
quotedArg.append(countPrecedingBackslashes(arg, i), '\\');
|
||
|
||
// Escape the double quote.
|
||
quotedArg.push_back('\\');
|
||
}
|
||
|
||
quotedArg.push_back(arg[i]);
|
||
}
|
||
|
||
// Make sure our final double quote doesn't get escaped by a trailing
|
||
// backslash.
|
||
quotedArg.append(countPrecedingBackslashes(arg, argLength), '\\');
|
||
quotedArg.push_back('"');
|
||
|
||
return quotedArg;
|
||
}
|
||
|
||
int executeAndWait(const char *commandLine) {
|
||
STARTUPINFO si;
|
||
ZeroMemory(&si, sizeof(si));
|
||
si.cb = sizeof(si);
|
||
|
||
PROCESS_INFORMATION pi;
|
||
ZeroMemory(&pi, sizeof(pi));
|
||
|
||
DWORD exitCode;
|
||
|
||
#if UNICODE
|
||
std::wstring wcommandLine;
|
||
if (!llvm::ConvertUTF8toWide(commandLine, wcommandLine))
|
||
return -3;
|
||
auto cmdline = const_cast<wchar_t *>(wcommandLine.data());
|
||
#else
|
||
auto cmdline = const_cast<char *>(commandLine);
|
||
#endif
|
||
// according to MSDN, only CreateProcessW (unicode) may modify the passed
|
||
// command line
|
||
if (!CreateProcess(NULL, cmdline, NULL, NULL, TRUE, 0,
|
||
NULL, NULL, &si, &pi)) {
|
||
exitCode = -1;
|
||
} else {
|
||
if (WaitForSingleObject(pi.hProcess, INFINITE) != 0 ||
|
||
!GetExitCodeProcess(pi.hProcess, &exitCode))
|
||
exitCode = -2;
|
||
|
||
CloseHandle(pi.hProcess);
|
||
CloseHandle(pi.hThread);
|
||
}
|
||
|
||
return exitCode;
|
||
}
|
||
|
||
bool setupMsvcEnvironment() {
|
||
if (getenv("VSINSTALLDIR"))
|
||
return true;
|
||
|
||
llvm::SmallString<128> tmpFilePath;
|
||
if (llvm::sys::fs::createTemporaryFile("ldc_dumpEnv", "", tmpFilePath))
|
||
return false;
|
||
|
||
/* Run `%ComSpec% /s /c "...\dumpEnv.bat <x86|amd64> > <tmpFilePath>"` to dump
|
||
* the MSVC environment to the temporary file.
|
||
*
|
||
* cmd.exe /c treats the following string argument (the command)
|
||
* in a very peculiar way if it starts with a double-quote.
|
||
* By adding /s and enclosing the command in extra double-quotes
|
||
* (WITHOUT additionally escaping the command), the command will
|
||
* be parsed properly.
|
||
*/
|
||
|
||
auto comspecEnv = getenv("ComSpec");
|
||
if (!comspecEnv) {
|
||
warning(Loc(),
|
||
"'ComSpec' environment variable is not set, assuming 'cmd.exe'.");
|
||
comspecEnv = "cmd.exe";
|
||
}
|
||
std::string cmdExecutable = comspecEnv;
|
||
std::string batchFile = exe_path::prependBinDir("dumpEnv.bat");
|
||
std::string arch =
|
||
global.params.targetTriple->isArch64Bit() ? "amd64" : "x86";
|
||
|
||
llvm::SmallString<512> commandLine;
|
||
commandLine += quoteArg(cmdExecutable);
|
||
commandLine += " /s /c \"";
|
||
commandLine += quoteArg(batchFile);
|
||
commandLine += ' ';
|
||
commandLine += arch;
|
||
commandLine += " > ";
|
||
commandLine += quoteArg(tmpFilePath);
|
||
commandLine += '"';
|
||
|
||
const int exitCode = executeAndWait(commandLine.c_str());
|
||
if (exitCode != 0) {
|
||
error(Loc(), "`%s` failed with status: %d", commandLine.c_str(), exitCode);
|
||
llvm::sys::fs::remove(tmpFilePath);
|
||
return false;
|
||
}
|
||
|
||
auto fileBuffer = llvm::MemoryBuffer::getFile(tmpFilePath);
|
||
llvm::sys::fs::remove(tmpFilePath);
|
||
if (fileBuffer.getError())
|
||
return false;
|
||
|
||
const auto contents = (*fileBuffer)->getBuffer();
|
||
const auto size = contents.size();
|
||
|
||
// Parse the file.
|
||
std::vector<std::pair<llvm::StringRef, llvm::StringRef>> env;
|
||
|
||
size_t i = 0;
|
||
// for each line
|
||
while (i < size) {
|
||
llvm::StringRef key, value;
|
||
|
||
for (size_t j = i; j < size; ++j) {
|
||
const char c = contents[j];
|
||
if (c == '=' && key.empty()) {
|
||
key = contents.slice(i, j);
|
||
i = j + 1;
|
||
} else if (c == '\n' || c == '\r' || c == '\0') {
|
||
if (!key.empty()) {
|
||
value = contents.slice(i, j);
|
||
}
|
||
// break and continue with next line
|
||
i = j + 1;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!key.empty() && !value.empty())
|
||
env.emplace_back(key, value);
|
||
}
|
||
|
||
if (global.params.verbose)
|
||
fprintf(global.stdmsg, "Applying environment variables:\n");
|
||
|
||
bool haveVsInstallDir = false;
|
||
|
||
for (const auto &pair : env) {
|
||
const std::string key = pair.first.str();
|
||
const std::string value = pair.second.str();
|
||
|
||
if (global.params.verbose)
|
||
fprintf(global.stdmsg, " %s=%s\n", key.c_str(), value.c_str());
|
||
|
||
SetEnvironmentVariableA(key.c_str(), value.c_str());
|
||
|
||
if (key == "VSINSTALLDIR")
|
||
haveVsInstallDir = true;
|
||
}
|
||
|
||
return haveVsInstallDir;
|
||
}
|
||
|
||
} // namespace windows
|
||
|
||
#endif
|
||
|
||
//////////////////////////////////////////////////////////////////////////////
|
||
|
||
static int linkObjToBinaryMSVC(bool sharedLib) {
|
||
Logger::println("*** Linking executable ***");
|
||
|
||
const std::string tool = "link.exe";
|
||
|
||
// build arguments
|
||
std::vector<std::string> args;
|
||
|
||
args.push_back("/NOLOGO");
|
||
|
||
// specify that the image will contain a table of safe exception handlers
|
||
// and can handle addresses >2GB (32bit only)
|
||
if (!global.params.is64bit) {
|
||
args.push_back("/SAFESEH");
|
||
args.push_back("/LARGEADDRESSAWARE");
|
||
}
|
||
|
||
// output debug information
|
||
if (global.params.symdebug) {
|
||
args.push_back("/DEBUG");
|
||
}
|
||
|
||
// enable Link-time Code Generation (aka. whole program optimization)
|
||
if (global.params.optimize) {
|
||
args.push_back("/LTCG");
|
||
}
|
||
|
||
// remove dead code and fold identical COMDATs
|
||
if (opts::disableLinkerStripDead) {
|
||
args.push_back("/OPT:NOREF");
|
||
} else {
|
||
args.push_back("/OPT:REF");
|
||
args.push_back("/OPT:ICF");
|
||
}
|
||
|
||
// specify creation of DLL
|
||
if (sharedLib) {
|
||
args.push_back("/DLL");
|
||
}
|
||
|
||
// output filename
|
||
std::string output = getOutputName(sharedLib);
|
||
|
||
args.push_back("/OUT:" + output);
|
||
|
||
appendObjectFiles(args);
|
||
|
||
// Link with profile-rt library when generating an instrumented binary
|
||
// profile-rt depends on Phobos (MD5 hashing).
|
||
if (global.params.genInstrProf) {
|
||
args.push_back("ldc-profile-rt.lib");
|
||
// profile-rt depends on ws2_32 for symbol `gethostname`
|
||
args.push_back("ws2_32.lib");
|
||
}
|
||
|
||
// user libs
|
||
for (unsigned i = 0; i < global.params.libfiles->dim; i++)
|
||
args.push_back((*global.params.libfiles)[i]);
|
||
|
||
// set the global gExePath
|
||
gExePath = output;
|
||
// assert(gExePath.isValid());
|
||
|
||
// create path to exe
|
||
CreateDirectoryOnDisk(gExePath);
|
||
|
||
// additional linker switches
|
||
for (unsigned i = 0; i < global.params.linkswitches->dim; i++) {
|
||
std::string str = global.params.linkswitches->data[i];
|
||
if (str.length() > 2) {
|
||
// rewrite common -L and -l switches
|
||
if (str[0] == '-' && str[1] == 'L') {
|
||
str = "/LIBPATH:" + str.substr(2);
|
||
} else if (str[0] == '-' && str[1] == 'l') {
|
||
str = str.substr(2) + ".lib";
|
||
}
|
||
}
|
||
args.push_back(str);
|
||
}
|
||
|
||
// default libs
|
||
// TODO check which libaries are necessary
|
||
args.push_back("kernel32.lib");
|
||
args.push_back("user32.lib");
|
||
args.push_back("gdi32.lib");
|
||
args.push_back("winspool.lib");
|
||
args.push_back("shell32.lib"); // required for dmain2.d
|
||
args.push_back("ole32.lib");
|
||
args.push_back("oleaut32.lib");
|
||
args.push_back("uuid.lib");
|
||
args.push_back("comdlg32.lib");
|
||
args.push_back("advapi32.lib");
|
||
|
||
Logger::println("Linking with: ");
|
||
Stream logstr = Logger::cout();
|
||
for (const auto &arg : args) {
|
||
if (!arg.empty()) {
|
||
logstr << "'" << arg << "'"
|
||
<< " ";
|
||
}
|
||
}
|
||
logstr << "\n"; // FIXME where's flush ?
|
||
|
||
#ifdef _WIN32
|
||
windows::setupMsvcEnvironment();
|
||
#endif
|
||
|
||
// try to call linker
|
||
return executeToolAndWait(tool, args, global.params.verbose);
|
||
}
|
||
|
||
//////////////////////////////////////////////////////////////////////////////
|
||
|
||
int linkObjToBinary() {
|
||
if (global.params.targetTriple->isWindowsMSVCEnvironment()) {
|
||
// TODO: Choose dynamic/static MSVCRT version based on staticFlag?
|
||
return linkObjToBinaryMSVC(global.params.dll);
|
||
}
|
||
|
||
return linkObjToBinaryGcc(global.params.dll, staticFlag);
|
||
}
|
||
|
||
//////////////////////////////////////////////////////////////////////////////
|
||
|
||
int createStaticLibrary() {
|
||
Logger::println("*** Creating static library ***");
|
||
|
||
const bool isTargetMSVC =
|
||
global.params.targetTriple->isWindowsMSVCEnvironment();
|
||
|
||
#if LDC_LLVM_VER >= 309
|
||
const bool useInternalArchiver = externalArchiver.empty();
|
||
#else
|
||
const bool useInternalArchiver = false;
|
||
#endif
|
||
|
||
// find archiver
|
||
std::string tool;
|
||
if (useInternalArchiver) {
|
||
tool = isTargetMSVC ? "llvm-lib.exe" : "llvm-ar";
|
||
} else if (!externalArchiver.empty()) {
|
||
tool = externalArchiver;
|
||
} else {
|
||
tool = isTargetMSVC ? "lib.exe" : getArchiver();
|
||
}
|
||
|
||
// build arguments
|
||
std::vector<std::string> args;
|
||
|
||
// ask ar to create a new library
|
||
if (!isTargetMSVC) {
|
||
args.push_back("rcs");
|
||
}
|
||
|
||
// ask lib to be quiet
|
||
if (isTargetMSVC) {
|
||
args.push_back("/NOLOGO");
|
||
}
|
||
|
||
// enable Link-time Code Generation (aka. whole program optimization)
|
||
if (isTargetMSVC && global.params.optimize) {
|
||
args.push_back("/LTCG");
|
||
}
|
||
|
||
// output filename
|
||
std::string libName;
|
||
if (global.params.libname) { // explicit
|
||
// DMD adds the default extension if there is none
|
||
libName = opts::invokedByLDMD
|
||
? FileName::defaultExt(global.params.libname, global.lib_ext)
|
||
: global.params.libname;
|
||
} else { // infer from first object file
|
||
libName = global.params.objfiles->dim
|
||
? FileName::removeExt((*global.params.objfiles)[0])
|
||
: "a.out";
|
||
libName += '.';
|
||
libName += global.lib_ext;
|
||
}
|
||
|
||
// DMD creates static libraries in the objects directory (unless using an
|
||
// absolute output path via `-of`).
|
||
if (opts::invokedByLDMD && global.params.objdir &&
|
||
!FileName::absolute(libName.c_str())) {
|
||
libName = FileName::combine(global.params.objdir, libName.c_str());
|
||
}
|
||
|
||
if (isTargetMSVC) {
|
||
args.push_back("/OUT:" + libName);
|
||
} else {
|
||
args.push_back(libName);
|
||
}
|
||
|
||
appendObjectFiles(args);
|
||
|
||
// create path to the library
|
||
CreateDirectoryOnDisk(libName);
|
||
|
||
#if LDC_LLVM_VER >= 309
|
||
if (useInternalArchiver) {
|
||
std::vector<const char *> fullArgs;
|
||
fullArgs.reserve(1 + args.size());
|
||
fullArgs.push_back(tool.c_str());
|
||
for (const auto &arg : args)
|
||
fullArgs.push_back(arg.c_str());
|
||
|
||
if (global.params.verbose) {
|
||
for (auto arg : fullArgs) {
|
||
fprintf(global.stdmsg, "%s ", arg);
|
||
}
|
||
fprintf(global.stdmsg, "\n");
|
||
fflush(global.stdmsg);
|
||
}
|
||
|
||
const int exitCode = isTargetMSVC ? ldc::lib(fullArgs) : ldc::ar(fullArgs);
|
||
if (exitCode)
|
||
error(Loc(), "%s failed with status: %d", tool.c_str(), exitCode);
|
||
|
||
return exitCode;
|
||
}
|
||
#endif
|
||
|
||
#ifdef _WIN32
|
||
if (isTargetMSVC)
|
||
windows::setupMsvcEnvironment();
|
||
#endif
|
||
|
||
// try to call archiver
|
||
return executeToolAndWait(tool, args, global.params.verbose);
|
||
}
|
||
|
||
//////////////////////////////////////////////////////////////////////////////
|
||
|
||
void deleteExeFile() {
|
||
if (!gExePath.empty() && !llvm::sys::fs::is_directory(gExePath)) {
|
||
llvm::sys::fs::remove(gExePath);
|
||
}
|
||
}
|
||
|
||
//////////////////////////////////////////////////////////////////////////////
|
||
|
||
int runProgram() {
|
||
assert(!gExePath.empty());
|
||
// assert(gExePath.isValid());
|
||
|
||
// Run executable
|
||
int status =
|
||
executeToolAndWait(gExePath, opts::runargs, global.params.verbose);
|
||
if (status < 0) {
|
||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||
error(Loc(), "program received signal %d", -status);
|
||
#else
|
||
error(Loc(), "program received signal %d (%s)", -status,
|
||
strsignal(-status));
|
||
#endif
|
||
return -status;
|
||
}
|
||
return status;
|
||
}
|