109 changes: 92 additions & 17 deletions lld/tools/lld/lld.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,27 @@
//===----------------------------------------------------------------------===//

#include "lld/Common/Driver.h"
#include "lld/Common/ErrorHandler.h"
#include "lld/Common/Memory.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/ADT/Triple.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/CrashRecoveryContext.h"
#include "llvm/Support/Host.h"
#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/PluginLoader.h"
#include "llvm/Support/Signals.h"
#include <cstdlib>

#if !defined(_MSC_VER) && !defined(__MINGW32__)
#include <signal.h> // for raise
#include <unistd.h> // for _exit
#endif

using namespace lld;
using namespace llvm;
using namespace llvm::sys;
Expand Down Expand Up @@ -133,36 +141,103 @@ static Flavor parseFlavor(std::vector<const char *> &v) {
return parseProgname(arg0);
}

// If this function returns true, lld calls _exit() so that it quickly
// exits without invoking destructors of globally allocated objects.
//
// We don't want to do that if we are running tests though, because
// doing that breaks leak sanitizer. So, lit sets this environment variable,
// and we use it to detect whether we are running tests or not.
static bool canExitEarly() { return StringRef(getenv("LLD_IN_TEST")) != "1"; }

/// Universal linker main(). This linker emulates the gnu, darwin, or
/// windows linker based on the argv[0] or -flavor option.
int main(int argc, const char **argv) {
InitLLVM x(argc, argv);

static int lldMain(int argc, const char **argv, llvm::raw_ostream &stdoutOS,
llvm::raw_ostream &stderrOS, bool exitEarly = true) {
std::vector<const char *> args(argv, argv + argc);
switch (parseFlavor(args)) {
case Gnu:
if (isPETarget(args))
return !mingw::link(args, canExitEarly(), llvm::outs(), llvm::errs());
return !elf::link(args, canExitEarly(), llvm::outs(), llvm::errs());
return !mingw::link(args, exitEarly, stdoutOS, stderrOS);
return !elf::link(args, exitEarly, stdoutOS, stderrOS);
case WinLink:
return !coff::link(args, canExitEarly(), llvm::outs(), llvm::errs());
return !coff::link(args, exitEarly, stdoutOS, stderrOS);
case Darwin:
return !mach_o::link(args, canExitEarly(), llvm::outs(), llvm::errs());
return !mach_o::link(args, exitEarly, stdoutOS, stderrOS);
case DarwinNew:
return !macho::link(args, canExitEarly(), llvm::outs(), llvm::errs());
return !macho::link(args, exitEarly, stdoutOS, stderrOS);
case Wasm:
return !wasm::link(args, canExitEarly(), llvm::outs(), llvm::errs());
return !lld::wasm::link(args, exitEarly, stdoutOS, stderrOS);
default:
die("lld is a generic driver.\n"
"Invoke ld.lld (Unix), ld64.lld (macOS), lld-link (Windows), wasm-ld"
" (WebAssembly) instead");
}
}

// Similar to lldMain except that exceptions are caught.
SafeReturn lld::safeLldMain(int argc, const char **argv,
llvm::raw_ostream &stdoutOS,
llvm::raw_ostream &stderrOS) {
int r = 0;
{
// The crash recovery is here only to be able to recover from arbitrary
// control flow when fatal() is called (through setjmp/longjmp or
// __try/__except).
llvm::CrashRecoveryContext crc;
if (!crc.RunSafely([&]() {
r = lldMain(argc, argv, stdoutOS, stderrOS, /*exitEarly=*/false);
}))
r = crc.RetCode;
}

// Cleanup memory and reset everything back in pristine condition. This path
// is only taken when LLD is in test, or when it is used as a library.
llvm::CrashRecoveryContext crc;
if (!crc.RunSafely([&]() { errorHandler().reset(); })) {
// The memory is corrupted beyond any possible recovery.
return {r, /*canRunAgain=*/false};
}
return {r, /*canRunAgain=*/true};
}

// When in lit tests, tells how many times the LLD tool should re-execute the
// main loop with the same inputs. When not in test, returns a value of 0 which
// signifies that LLD shall not release any memory after execution, to speed up
// process destruction.
static unsigned inTestVerbosity() {
unsigned v = 0;
StringRef(getenv("LLD_IN_TEST")).getAsInteger(10, v);
return v;
}

int main(int argc, const char **argv) {
InitLLVM x(argc, argv);

// Not running in lit tests, just take the shortest codepath with global
// exception handling and no memory cleanup on exit.
if (!inTestVerbosity())
return lldMain(argc, argv, llvm::outs(), llvm::errs());

Optional<int> mainRet;
CrashRecoveryContext::Enable();

for (unsigned i = inTestVerbosity(); i > 0; --i) {
// Disable stdout/stderr for all iterations but the last one.
if (i != 1)
errorHandler().disableOutput = true;

// Execute one iteration.
auto r = safeLldMain(argc, argv, llvm::outs(), llvm::errs());
if (!r.canRunAgain)
_exit(r.ret); // Exit now, can't re-execute again.

if (!mainRet) {
mainRet = r.ret;
} else if (r.ret != *mainRet) {
// Exit now, to fail the tests if the result is different between runs.
return r.ret;
}
}
#if LLVM_ON_UNIX
// Re-throw the signal so it can be caught by WIFSIGNALED in
// llvm/lib/Support/Unix/Program.inc. This is required to correctly handle
// usages of `not --crash`.
if (*mainRet > 128) {
llvm::sys::unregisterHandlers();
raise(*mainRet - 128);
}
#endif
return *mainRet;
}
4 changes: 3 additions & 1 deletion lld/wasm/Driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ bool link(ArrayRef<const char *> args, bool canExitEarly, raw_ostream &stdoutOS,
lld::stdoutOS = &stdoutOS;
lld::stderrOS = &stderrOS;

errorHandler().cleanupCallback = []() { freeArena(); };

errorHandler().logName = args::getFilenameWithoutExe(args[0]);
errorHandler().errorLimitExceededMsg =
"too many errors emitted, stopping now (use "
Expand All @@ -103,7 +105,6 @@ bool link(ArrayRef<const char *> args, bool canExitEarly, raw_ostream &stdoutOS,
if (canExitEarly)
exitLld(errorCount() ? 1 : 0);

freeArena();
return !errorCount();
}

Expand Down Expand Up @@ -776,6 +777,7 @@ void LinkerDriver::link(ArrayRef<const char *> argsArr) {
v.push_back("wasm-ld (LLVM option parsing)");
for (auto *arg : args.filtered(OPT_mllvm))
v.push_back(arg->getValue());
cl::ResetAllOptionOccurrences();
cl::ParseCommandLineOptions(v.size(), v.data());

errorHandler().errorLimit = args::getInteger(args, OPT_error_limit, 20);
Expand Down