diff --git a/lld/CMakeLists.txt b/lld/CMakeLists.txt --- a/lld/CMakeLists.txt +++ b/lld/CMakeLists.txt @@ -204,6 +204,8 @@ add_subdirectory(tools/lld) if (LLVM_INCLUDE_TESTS) + add_custom_target(LLDUnitTests) + llvm_add_unittests(LLD_UNITTESTS_ADDED) add_subdirectory(test) endif() diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp --- a/lld/COFF/Driver.cpp +++ b/lld/COFF/Driver.cpp @@ -63,7 +63,7 @@ bool link(ArrayRef args, llvm::raw_ostream &stdoutOS, llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput) { - // This driver-specific context will be freed later by lldMain(). + // This driver-specific context will be freed later by unsafeLldMain(). auto *ctx = new COFFLinkerContext; ctx->e.initialize(stdoutOS, stderrOS, exitEarly, disableOutput); diff --git a/lld/COFF/Options.td b/lld/COFF/Options.td --- a/lld/COFF/Options.td +++ b/lld/COFF/Options.td @@ -293,6 +293,9 @@ def show_timing : F<"time">; def summary : F<"summary">; +// Flag that selects the driver, it should be ignored. +def flavor : Separate<["-"], "flavor">; + //============================================================================== // The flags below do nothing. They are defined only for link.exe compatibility. //============================================================================== diff --git a/lld/Common/CMakeLists.txt b/lld/Common/CMakeLists.txt --- a/lld/Common/CMakeLists.txt +++ b/lld/Common/CMakeLists.txt @@ -26,6 +26,7 @@ DWARF.cpp ErrorHandler.cpp Filesystem.cpp + DriversMain.cpp Memory.cpp Reproduce.cpp Strings.cpp diff --git a/lld/Common/DriversMain.cpp b/lld/Common/DriversMain.cpp new file mode 100644 --- /dev/null +++ b/lld/Common/DriversMain.cpp @@ -0,0 +1,209 @@ +//===- LLDLibrary.cpp - Use LLD as a library ------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lld/Common/CommonLinkerContext.h" +#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/Process.h" +#include + +using namespace lld; +using namespace llvm; +using namespace llvm::sys; + +static void err(const Twine &s) { llvm::errs() << s << "\n"; } + +static Flavor getFlavor(StringRef s) { + return StringSwitch(s) + .CasesLower("ld", "ld.lld", "gnu", Gnu) + .CasesLower("wasm", "ld-wasm", Wasm) + .CaseLower("link", WinLink) + .CasesLower("ld64", "ld64.lld", "darwin", Darwin) + .Default(Invalid); +} + +static cl::TokenizerCallback getDefaultQuotingStyle() { + if (Triple(sys::getProcessTriple()).getOS() == Triple::Win32) + return cl::TokenizeWindowsCommandLine; + return cl::TokenizeGNUCommandLine; +} + +static bool isPETargetName(StringRef s) { + return s == "i386pe" || s == "i386pep" || s == "thumb2pe" || s == "arm64pe"; +} + +static std::optional isPETarget(llvm::ArrayRef args) { + for (auto it = args.begin(); it + 1 != args.end(); ++it) { + if (StringRef(*it) != "-m") + continue; + return isPETargetName(*(it + 1)); + } + + // Expand response files (arguments in the form of @) + // to allow detecting the -m argument from arguments in them. + SmallVector expandedArgs(args.data(), + args.data() + args.size()); + BumpPtrAllocator a; + StringSaver saver(a); + cl::ExpansionContext ectx(saver.getAllocator(), getDefaultQuotingStyle()); + if (Error e = ectx.expandResponseFiles(expandedArgs)) { + err(toString(std::move(e))); + return std::nullopt; + } + + for (auto it = expandedArgs.begin(); it + 1 != expandedArgs.end(); ++it) { + if (StringRef(*it) != "-m") + continue; + return isPETargetName(*(it + 1)); + } + +#ifdef LLD_DEFAULT_LD_LLD_IS_MINGW + return true; +#else + return false; +#endif +} + +static Flavor parseProgname(StringRef progname) { + // Use GNU driver for "ld" by default. + if (progname == "ld") + return Gnu; + + // Progname may be something like "lld-gnu". Parse it. + SmallVector v; + progname.split(v, "-"); + for (StringRef s : v) + if (Flavor f = getFlavor(s)) + return f; + return Invalid; +} + +static Flavor parseFlavorWithoutMinGW(llvm::ArrayRef args) { + // Parse -flavor option. + if (args.size() > 1 && args[1] == StringRef("-flavor")) { + if (args.size() <= 2) { + err("missing arg value for '-flavor'"); + return Invalid; + } + Flavor f = getFlavor(args[2]); + if (f == Invalid) { + err("Unknown flavor: " + StringRef(args[2])); + return Invalid; + } + return f; + } + + // Deduct the flavor from argv[0]. + StringRef arg0 = path::filename(args[0]); + if (arg0.endswith_insensitive(".exe")) + arg0 = arg0.drop_back(4); + Flavor f = parseProgname(arg0); + if (f == Invalid) { + err("lld is a generic driver.\n" + "Invoke ld.lld (Unix), ld64.lld (macOS), lld-link (Windows), wasm-ld" + " (WebAssembly) instead"); + return Invalid; + } + return f; +} + +static Flavor parseFlavor(llvm::ArrayRef args) { + Flavor f = parseFlavorWithoutMinGW(args); + if (f == Gnu) { + auto isPE = isPETarget(args); + if (!isPE) + return Invalid; + if (*isPE) + return MinGW; + } + return f; +} + +static Driver whichDriver(llvm::ArrayRef args, + llvm::ArrayRef drivers) { + Flavor f = parseFlavor(args); + auto it = + llvm::find_if(drivers, [=](auto &driverdef) { return driverdef.f == f; }); + if (it == drivers.end()) { + // Driver is invalid or not available in this build. + return [](llvm::ArrayRef, llvm::raw_ostream &, + llvm::raw_ostream &, bool, bool) { return false; }; + } + return it->d; +} + +namespace lld { +bool inTestOutputDisabled = false; +} + +/// Universal linker main(). This linker emulates the gnu, darwin, or +/// windows linker based on the argv[0] or -flavor option. +static int unsafeLldMain(llvm::ArrayRef args, + llvm::raw_ostream &stdoutOS, + llvm::raw_ostream &stderrOS, + llvm::ArrayRef drivers, bool exitEarly) { + + // Run the driver. If an error occurs, false will be returned. + Driver d = whichDriver(args, drivers); + int r = !d(args, stdoutOS, stderrOS, exitEarly, inTestOutputDisabled); + + // Call exit() if we can to avoid calling destructors. + if (exitEarly) + exitLld(r); + + // Delete the global context and clear the global context pointer, so that it + // cannot be accessed anymore. + CommonLinkerContext::destroy(); + + return r; +} + +namespace lld { +Result lldMain(llvm::ArrayRef args, llvm::raw_ostream &stdoutOS, + llvm::raw_ostream &stderrOS, llvm::ArrayRef drivers, + bool libUsage) { + if (!libUsage) { + int r = + unsafeLldMain(args, stdoutOS, stderrOS, drivers, /*exitEarly=*/true); + return {r, /*canRunAgain=*/false}; + } + + 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 = unsafeLldMain(args, stdoutOS, stderrOS, drivers, + /*exitEarly=*/false); + })) + return {crc.RetCode, /*canRunAgain=*/false}; + } + + // 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([&]() { CommonLinkerContext::destroy(); })) { + // The memory is corrupted beyond any possible recovery. + return {r, /*canRunAgain=*/false}; + } + return {r, /*canRunAgain=*/true}; +} +} // namespace lld diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -106,10 +106,11 @@ needsTlsLd.store(false, std::memory_order_relaxed); } -bool elf::link(ArrayRef args, llvm::raw_ostream &stdoutOS, - llvm::raw_ostream &stderrOS, bool exitEarly, - bool disableOutput) { - // This driver-specific context will be freed later by lldMain(). +namespace lld { +namespace elf { +bool link(ArrayRef args, llvm::raw_ostream &stdoutOS, + llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput) { + // This driver-specific context will be freed later by unsafeLldMain(). auto *ctx = new CommonLinkerContext; ctx->e.initialize(stdoutOS, stderrOS, exitEarly, disableOutput); @@ -146,6 +147,8 @@ return errorCount() == 0; } +} // namespace elf +} // namespace lld // Parses a linker -m option. static std::tuple parseEmulation(StringRef emul) { diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td --- a/lld/ELF/Options.td +++ b/lld/ELF/Options.td @@ -734,3 +734,6 @@ "Instruct the dynamic loader to enable MTE protection for the heap", "">; defm android_memtag_mode: EEq<"android-memtag-mode", "Instruct the dynamic loader to start under MTE mode {async, sync, none}">; + +// Flag that selects the driver, it should be ignored. +def flavor : Separate<["-"], "flavor">; diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp --- a/lld/MachO/Driver.cpp +++ b/lld/MachO/Driver.cpp @@ -1362,9 +1362,10 @@ } } -bool macho::link(ArrayRef argsArr, llvm::raw_ostream &stdoutOS, - llvm::raw_ostream &stderrOS, bool exitEarly, - bool disableOutput) { +namespace lld { +namespace macho { +bool link(ArrayRef argsArr, llvm::raw_ostream &stdoutOS, + llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput) { // This driver-specific context will be freed later by lldMain(). auto *ctx = new CommonLinkerContext; @@ -1948,3 +1949,5 @@ return errorCount() == 0; } +} // namespace macho +} // namespace lld diff --git a/lld/MachO/Options.td b/lld/MachO/Options.td --- a/lld/MachO/Options.td +++ b/lld/MachO/Options.td @@ -1383,3 +1383,6 @@ HelpText<"This option only applies to i386 in ld64">, Flags<[HelpHidden]>, Group; + +// Flag that selects the driver, it should be ignored. +def flavor : Separate<["-"], "flavor">; diff --git a/lld/MinGW/Driver.cpp b/lld/MinGW/Driver.cpp --- a/lld/MinGW/Driver.cpp +++ b/lld/MinGW/Driver.cpp @@ -157,11 +157,17 @@ return ""; } +namespace lld { +namespace coff { +bool link(ArrayRef argsArr, llvm::raw_ostream &stdoutOS, + llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput); +} + +namespace mingw { // Convert Unix-ish command line arguments to Windows-ish ones and // then call coff::link. -bool mingw::link(ArrayRef argsArr, llvm::raw_ostream &stdoutOS, - llvm::raw_ostream &stderrOS, bool exitEarly, - bool disableOutput) { +bool link(ArrayRef argsArr, llvm::raw_ostream &stdoutOS, + llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput) { auto *ctx = new CommonLinkerContext; ctx->e.initialize(stdoutOS, stderrOS, exitEarly, disableOutput); @@ -482,3 +488,5 @@ return coff::link(vec, stdoutOS, stderrOS, exitEarly, disableOutput); } +} // namespace mingw +} // namespace lld diff --git a/lld/MinGW/Options.td b/lld/MinGW/Options.td --- a/lld/MinGW/Options.td +++ b/lld/MinGW/Options.td @@ -177,3 +177,6 @@ defm: EqNoHelp<"plugin-opt">; defm: EqNoHelp<"sysroot">; def: F<"start-group">; + +// Flag that selects the driver, it should be ignored. +def flavor : Separate<["-"], "flavor">; diff --git a/lld/include/lld/Common/Driver.h b/lld/include/lld/Common/Driver.h --- a/lld/include/lld/Common/Driver.h +++ b/lld/include/lld/Common/Driver.h @@ -13,8 +13,25 @@ #include "llvm/Support/raw_ostream.h" namespace lld { -struct SafeReturn { - int ret; +enum Flavor { + Invalid, + Gnu, // -flavor gnu + MinGW, // -flavor gnu MinGW + WinLink, // -flavor link + Darwin, // -flavor darwin + Wasm, // -flavor wasm +}; + +using Driver = bool (*)(llvm::ArrayRef, llvm::raw_ostream &, + llvm::raw_ostream &, bool, bool); + +struct DriverDef { + Flavor f; + Driver d; +}; + +struct Result { + int retCode; bool canRunAgain; }; @@ -24,33 +41,30 @@ // and re-entry would not be possible anymore. Use exitLld() in that case to // properly exit your application and avoid intermittent crashes on exit caused // by cleanup. -SafeReturn safeLldMain(int argc, const char **argv, llvm::raw_ostream &stdoutOS, - llvm::raw_ostream &stderrOS); - -namespace coff { -bool link(llvm::ArrayRef args, llvm::raw_ostream &stdoutOS, - llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput); -} - -namespace mingw { -bool link(llvm::ArrayRef args, llvm::raw_ostream &stdoutOS, - llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput); -} - -namespace elf { -bool link(llvm::ArrayRef args, llvm::raw_ostream &stdoutOS, - llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput); -} - -namespace macho { -bool link(llvm::ArrayRef args, llvm::raw_ostream &stdoutOS, - llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput); -} - -namespace wasm { -bool link(llvm::ArrayRef args, llvm::raw_ostream &stdoutOS, - llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput); -} +Result lldMain(llvm::ArrayRef args, llvm::raw_ostream &stdoutOS, + llvm::raw_ostream &stderrOS, llvm::ArrayRef drivers, + bool libUsage = true); } // namespace lld +// With this macro, library users must specify which drivers they use, provide +// that information to lldMain() in the `drivers` param, and link the +// corresponding driver library in their executable. +#define LLD_HAS_DRIVER(name) \ + namespace lld { \ + namespace name { \ + bool link(llvm::ArrayRef args, llvm::raw_ostream &stdoutOS, \ + llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput); \ + } \ + } + +// An array which declares that all LLD drivers are linked in your executable. +// Must be used along with LLD_HAS_DRIVERS. See examples in LLD unittests. +#define LLD_ALL_DRIVERS \ + { \ + {lld::WinLink, &lld::coff::link}, {lld::Gnu, &lld::elf::link}, \ + {lld::MinGW, &lld::mingw::link}, {lld::Darwin, &lld::macho::link}, { \ + lld::Wasm, &lld::wasm::link \ + } \ + } + #endif diff --git a/lld/test/CMakeLists.txt b/lld/test/CMakeLists.txt --- a/lld/test/CMakeLists.txt +++ b/lld/test/CMakeLists.txt @@ -16,11 +16,18 @@ MAIN_CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/lit.cfg.py ) +configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/Unit/lit.site.cfg.py.in + ${CMAKE_CURRENT_BINARY_DIR}/Unit/lit.site.cfg.py + MAIN_CONFIG + ${CMAKE_CURRENT_SOURCE_DIR}/Unit/lit.cfg.py + ) set(LLD_TEST_DEPS lld) if (NOT LLD_BUILT_STANDALONE) list(APPEND LLD_TEST_DEPS FileCheck + LLDUnitTests count dsymutil llc @@ -61,6 +68,7 @@ ${CMAKE_CURRENT_BINARY_DIR} DEPENDS ${LLD_TEST_DEPS} ) +set_target_properties(check-lld PROPERTIES FOLDER "lld tests") add_custom_target(lld-test-depends DEPENDS ${LLD_TEST_DEPS}) set_target_properties(lld-test-depends PROPERTIES FOLDER "lld tests") @@ -69,8 +77,6 @@ DEPENDS ${LLD_TEST_DEPS} ) -set_target_properties(check-lld PROPERTIES FOLDER "lld tests") - # Add a legacy target spelling: lld-test add_custom_target(lld-test) add_dependencies(lld-test check-lld) diff --git a/lld/test/Unit/lit.cfg.py b/lld/test/Unit/lit.cfg.py new file mode 100644 --- /dev/null +++ b/lld/test/Unit/lit.cfg.py @@ -0,0 +1,43 @@ +# -*- Python -*- + +# Configuration file for the 'lit' test runner. + +import os +import subprocess + +import lit.formats + +# name: The name of this test suite. +config.name = 'LLD-Unit' + +# suffixes: A list of file extensions to treat as test files. +config.suffixes = [] + +# test_source_root: The root path where tests are located. +# test_exec_root: The root path where tests should be run. +config.test_exec_root = os.path.join(config.lld_obj_root, 'unittests') +config.test_source_root = config.test_exec_root + +# testFormat: The test format to use to interpret tests. +config.test_format = lit.formats.GoogleTest(config.llvm_build_mode, 'Tests') + +# Propagate the temp directory. Windows requires this because it uses \Windows\ +# if none of these are present. +if 'TMP' in os.environ: + config.environment['TMP'] = os.environ['TMP'] +if 'TEMP' in os.environ: + config.environment['TEMP'] = os.environ['TEMP'] + +# Propagate HOME as it can be used to override incorrect homedir in passwd +# that causes the tests to fail. +if 'HOME' in os.environ: + config.environment['HOME'] = os.environ['HOME'] + +# Win32 seeks DLLs along %PATH%. +if sys.platform in ['win32', 'cygwin'] and os.path.isdir(config.shlibdir): + config.environment['PATH'] = os.path.pathsep.join(( + config.shlibdir, config.environment['PATH'])) + +# Win32 may use %SYSTEMDRIVE% during file system shell operations, so propogate. +if sys.platform == 'win32' and 'SYSTEMDRIVE' in os.environ: + config.environment['SYSTEMDRIVE'] = os.environ['SYSTEMDRIVE'] diff --git a/lld/test/Unit/lit.site.cfg.py.in b/lld/test/Unit/lit.site.cfg.py.in new file mode 100644 --- /dev/null +++ b/lld/test/Unit/lit.site.cfg.py.in @@ -0,0 +1,16 @@ +@LIT_SITE_CFG_IN_HEADER@ + +import sys + +config.llvm_src_root = path(r"@LLVM_SOURCE_DIR@") +config.llvm_obj_root = path(r"@LLVM_BINARY_DIR@") +config.llvm_tools_dir = lit_config.substitute(path(r"@LLVM_TOOLS_DIR@")) +config.llvm_build_mode = lit_config.substitute("@LLVM_BUILD_MODE@") +config.enable_shared = @ENABLE_SHARED@ +config.shlibdir = lit_config.substitute(path(r"@SHLIBDIR@")) +config.lld_obj_root = "@LLD_BINARY_DIR@" +config.lld_libs_dir = lit_config.substitute("@CURRENT_LIBS_DIR@") +config.lld_tools_dir = lit_config.substitute("@CURRENT_TOOLS_DIR@") + +# Let the main config do the real work. +lit_config.load_config(config, "@LLD_SOURCE_DIR@/test/Unit/lit.cfg.py") diff --git a/lld/tools/lld/lld.cpp b/lld/tools/lld/lld.cpp --- a/lld/tools/lld/lld.cpp +++ b/lld/tools/lld/lld.cpp @@ -25,7 +25,6 @@ // //===----------------------------------------------------------------------===// -#include "lld/Common/CommonLinkerContext.h" #include "lld/Common/Driver.h" #include "lld/Common/ErrorHandler.h" #include "lld/Common/Memory.h" @@ -48,161 +47,9 @@ using namespace llvm; using namespace llvm::sys; -enum Flavor { - Invalid, - Gnu, // -flavor gnu - WinLink, // -flavor link - Darwin, // -flavor darwin - Wasm, // -flavor wasm -}; - -[[noreturn]] static void die(const Twine &s) { - llvm::errs() << s << "\n"; - exit(1); -} - -static Flavor getFlavor(StringRef s) { - return StringSwitch(s) - .CasesLower("ld", "ld.lld", "gnu", Gnu) - .CasesLower("wasm", "ld-wasm", Wasm) - .CaseLower("link", WinLink) - .CasesLower("ld64", "ld64.lld", "darwin", Darwin) - .Default(Invalid); -} - -static cl::TokenizerCallback getDefaultQuotingStyle() { - if (Triple(sys::getProcessTriple()).getOS() == Triple::Win32) - return cl::TokenizeWindowsCommandLine; - return cl::TokenizeGNUCommandLine; -} - -static bool isPETargetName(StringRef s) { - return s == "i386pe" || s == "i386pep" || s == "thumb2pe" || s == "arm64pe"; -} - -static bool isPETarget(std::vector &v) { - for (auto it = v.begin(); it + 1 != v.end(); ++it) { - if (StringRef(*it) != "-m") - continue; - return isPETargetName(*(it + 1)); - } - // Expand response files (arguments in the form of @) - // to allow detecting the -m argument from arguments in them. - SmallVector expandedArgs(v.data(), v.data() + v.size()); - BumpPtrAllocator a; - StringSaver saver(a); - cl::ExpansionContext ECtx(saver.getAllocator(), getDefaultQuotingStyle()); - if (Error Err = ECtx.expandResponseFiles(expandedArgs)) - die(toString(std::move(Err))); - for (auto it = expandedArgs.begin(); it + 1 != expandedArgs.end(); ++it) { - if (StringRef(*it) != "-m") - continue; - return isPETargetName(*(it + 1)); - } - -#ifdef LLD_DEFAULT_LD_LLD_IS_MINGW - return true; -#else - return false; -#endif -} - -static Flavor parseProgname(StringRef progname) { - // Use GNU driver for "ld" by default. - if (progname == "ld") - return Gnu; - - // Progname may be something like "lld-gnu". Parse it. - SmallVector v; - progname.split(v, "-"); - for (StringRef s : v) - if (Flavor f = getFlavor(s)) - return f; - return Invalid; -} - -static Flavor parseFlavor(std::vector &v) { - // Parse -flavor option. - if (v.size() > 1 && v[1] == StringRef("-flavor")) { - if (v.size() <= 2) - die("missing arg value for '-flavor'"); - Flavor f = getFlavor(v[2]); - if (f == Invalid) - die("Unknown flavor: " + StringRef(v[2])); - v.erase(v.begin() + 1, v.begin() + 3); - return f; - } - - // Deduct the flavor from argv[0]. - StringRef arg0 = path::filename(v[0]); - if (arg0.endswith_insensitive(".exe")) - arg0 = arg0.drop_back(4); - return parseProgname(arg0); -} - -bool inTestOutputDisabled = false; - -/// Universal linker main(). This linker emulates the gnu, darwin, or -/// windows linker based on the argv[0] or -flavor option. -static int lldMain(int argc, const char **argv, llvm::raw_ostream &stdoutOS, - llvm::raw_ostream &stderrOS, bool exitEarly = true) { - std::vector args(argv, argv + argc); - auto link = [&args]() { - Flavor f = parseFlavor(args); - if (f == Gnu && isPETarget(args)) - return mingw::link; - else if (f == Gnu) - return elf::link; - else if (f == WinLink) - return coff::link; - else if (f == Darwin) - return macho::link; - else if (f == Wasm) - return lld::wasm::link; - else - die("lld is a generic driver.\n" - "Invoke ld.lld (Unix), ld64.lld (macOS), lld-link (Windows), wasm-ld" - " (WebAssembly) instead"); - }(); - // Run the driver. If an error occurs, false will be returned. - bool r = link(args, stdoutOS, stderrOS, exitEarly, inTestOutputDisabled); - - // Call exit() if we can to avoid calling destructors. - if (exitEarly) - exitLld(!r ? 1 : 0); - - // Delete the global context and clear the global context pointer, so that it - // cannot be accessed anymore. - CommonLinkerContext::destroy(); - - return !r ? 1 : 0; -} - -// 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); - })) - return {crc.RetCode, /*canRunAgain=*/false}; - } - - // 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([&]() { CommonLinkerContext::destroy(); })) { - // The memory is corrupted beyond any possible recovery. - return {r, /*canRunAgain=*/false}; - } - return {r, /*canRunAgain=*/true}; -} +namespace lld { +extern bool inTestOutputDisabled; +} // namespace lld // 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 @@ -214,6 +61,12 @@ return v; } +LLD_HAS_DRIVER(coff); +LLD_HAS_DRIVER(elf); +LLD_HAS_DRIVER(mingw); +LLD_HAS_DRIVER(macho); +LLD_HAS_DRIVER(wasm); + int lld_main(int argc, char **argv) { InitLLVM x(argc, argv); sys::Process::UseANSIEscapeCodes(true); @@ -224,11 +77,15 @@ LLVM_BUILTIN_TRAP; } + ArrayRef args(argv, argv + argc); + // 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, const_cast(argv), llvm::outs(), - llvm::errs()); + if (!inTestVerbosity()) { + lld::Result r = lldMain(args, llvm::outs(), llvm::errs(), LLD_ALL_DRIVERS, + /*libUsage=*/false); + return r.retCode; + } std::optional mainRet; CrashRecoveryContext::Enable(); @@ -238,16 +95,15 @@ inTestOutputDisabled = (i != 1); // Execute one iteration. - auto r = safeLldMain(argc, const_cast(argv), llvm::outs(), - llvm::errs()); + auto r = lldMain(args, llvm::outs(), llvm::errs(), LLD_ALL_DRIVERS); if (!r.canRunAgain) - exitLld(r.ret); // Exit now, can't re-execute again. + exitLld(r.retCode); // Exit now, can't re-execute again. if (!mainRet) { - mainRet = r.ret; - } else if (r.ret != *mainRet) { + mainRet = r.retCode; + } else if (r.retCode != *mainRet) { // Exit now, to fail the tests if the result is different between runs. - return r.ret; + return r.retCode; } } return *mainRet; diff --git a/lld/unittests/AsLibAll/AllDrivers.cpp b/lld/unittests/AsLibAll/AllDrivers.cpp new file mode 100644 --- /dev/null +++ b/lld/unittests/AsLibAll/AllDrivers.cpp @@ -0,0 +1,31 @@ +//===- AllDrivers.cpp -------------------------------------------*- C++ -*-===// +// +// This file is licensed under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lld/Common/Driver.h" +#include "gmock/gmock.h" + +LLD_HAS_DRIVER(coff); +LLD_HAS_DRIVER(elf); +LLD_HAS_DRIVER(mingw); +LLD_HAS_DRIVER(macho); +LLD_HAS_DRIVER(wasm); + +bool lldInvoke(std::vector args) { + args.push_back("--version"); + lld::Result r = + lld::lldMain(args, llvm::outs(), llvm::errs(), LLD_ALL_DRIVERS); + return !r.retCode && r.canRunAgain; +} + +TEST(AsLib, AllDrivers) { + EXPECT_TRUE(lldInvoke({"ld.lld"})); + EXPECT_TRUE(lldInvoke({"ld64.lld"})); + EXPECT_TRUE(lldInvoke({"ld", "-m", "i386pe"})); // MinGW + EXPECT_TRUE(lldInvoke({"lld-link"})); + EXPECT_TRUE(lldInvoke({"wasm-ld"})); +} diff --git a/lld/unittests/AsLibAll/CMakeLists.txt b/lld/unittests/AsLibAll/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/lld/unittests/AsLibAll/CMakeLists.txt @@ -0,0 +1,13 @@ +add_lld_unittests(LLDAsLibAllTests + AllDrivers.cpp +) + +target_link_libraries(LLDAsLibAllTests + PRIVATE + lldCommon + lldCOFF + lldELF + lldMachO + lldMinGW + lldWasm +) diff --git a/lld/unittests/AsLibELF/CMakeLists.txt b/lld/unittests/AsLibELF/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/lld/unittests/AsLibELF/CMakeLists.txt @@ -0,0 +1,10 @@ +add_lld_unittests(LLDAsLibELFTests + ROCm.cpp + SomeDrivers.cpp +) + +target_link_libraries(LLDAsLibELFTests + PRIVATE + lldCommon + lldELF +) diff --git a/lld/unittests/AsLibELF/Inputs/kernel1.o b/lld/unittests/AsLibELF/Inputs/kernel1.o new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@ + +static std::string expand(const char *path) { + llvm::StringRef thisFile = llvm::sys::path::parent_path(__FILE__); + std::string expanded = path; + if (llvm::StringRef(path).contains("%")) { + expanded.replace(expanded.find("%S"), 2, thisFile.data(), thisFile.size()); + } + return expanded; +} + +LLD_HAS_DRIVER(elf); + +bool lldInvoke(const char *inPath, const char *outPath) { + std::vector args{"ld.lld", "-shared", inPath, "-o", outPath}; + lld::Result s = lld::lldMain(args, llvm::outs(), llvm::errs(), + {{lld::Gnu, &lld::elf::link}}); + return !s.retCode && s.canRunAgain; +} + +static bool runLinker(const char *path) { + // Create a temp file for HSA code object. + int tempHsacoFD = -1; + llvm::SmallString<128> tempHsacoFilename; + if (llvm::sys::fs::createTemporaryFile("kernel", "hsaco", tempHsacoFD, + tempHsacoFilename)) { + return false; + } + llvm::FileRemover cleanupHsaco(tempHsacoFilename); + // Invoke lld. Expect a true return value from lld. + std::string expandedPath = expand(path); + if (!lldInvoke(expandedPath.data(), tempHsacoFilename.c_str())) { + llvm::errs() << "Failed to link: " << expandedPath << "\n"; + return false; + } + return true; +} + +TEST(AsLib, ROCm) { + EXPECT_TRUE(runLinker("%S/Inputs/kernel1.o")); + EXPECT_TRUE(runLinker("%S/Inputs/kernel2.o")); + EXPECT_TRUE(runLinker("%S/Inputs/kernel1.o")); + EXPECT_TRUE(runLinker("%S/Inputs/kernel2.o")); +} diff --git a/lld/unittests/AsLibELF/SomeDrivers.cpp b/lld/unittests/AsLibELF/SomeDrivers.cpp new file mode 100644 --- /dev/null +++ b/lld/unittests/AsLibELF/SomeDrivers.cpp @@ -0,0 +1,27 @@ +//===- SomeDrivers.cpp ------------------------------------------*- C++ -*-===// +// +// This file is licensed under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lld/Common/Driver.h" +#include "gmock/gmock.h" + +LLD_HAS_DRIVER(elf); + +bool lldInvoke(const char *lldExe) { + std::vector args{lldExe, "--version"}; + lld::Result s = lld::lldMain(args, llvm::outs(), llvm::errs(), + {{lld::Gnu, &lld::elf::link}}); + return !s.retCode && s.canRunAgain; +} + +TEST(AsLib, AllDrivers) { + EXPECT_TRUE(lldInvoke("ld.lld")); // ELF + // These drivers are not linked in this unit test. + EXPECT_FALSE(lldInvoke("ld64.lld")); // Mach-O + EXPECT_FALSE(lldInvoke("lld-link")); // COFF + EXPECT_FALSE(lldInvoke("wasm-ld")); // Wasm +} diff --git a/lld/unittests/CMakeLists.txt b/lld/unittests/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/lld/unittests/CMakeLists.txt @@ -0,0 +1,8 @@ +set_target_properties(LLDUnitTests PROPERTIES FOLDER "lld tests") + +function(add_lld_unittests test_dirname) + add_unittest(LLDUnitTests ${test_dirname} ${ARGN}) +endfunction() + +add_subdirectory(AsLibAll) +add_subdirectory(AsLibELF) diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp --- a/lld/wasm/Driver.cpp +++ b/lld/wasm/Driver.cpp @@ -85,7 +85,7 @@ bool link(ArrayRef args, llvm::raw_ostream &stdoutOS, llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput) { - // This driver-specific context will be freed later by lldMain(). + // This driver-specific context will be freed later by unsafeLldMain(). auto *ctx = new CommonLinkerContext; ctx->e.initialize(stdoutOS, stderrOS, exitEarly, disableOutput); diff --git a/lld/wasm/Options.td b/lld/wasm/Options.td --- a/lld/wasm/Options.td +++ b/lld/wasm/Options.td @@ -271,3 +271,6 @@ // Experimental PIC mode. def experimental_pic: FF<"experimental-pic">, HelpText<"Enable Experimental PIC">; + +// Flag that selects the driver, it should be ignored. +def flavor : Separate<["-"], "flavor">; diff --git a/llvm/cmake/modules/AddLLVM.cmake b/llvm/cmake/modules/AddLLVM.cmake --- a/llvm/cmake/modules/AddLLVM.cmake +++ b/llvm/cmake/modules/AddLLVM.cmake @@ -2424,3 +2424,13 @@ set(${exe_var_name} "${exe_name}" CACHE STRING "") set(${target_var_name} "${target_name}" CACHE STRING "") endfunction() + +# Adds the unittests folder if gtest is available. +function(llvm_add_unittests tests_added) + if (EXISTS ${LLVM_THIRD_PARTY_DIR}/unittest/googletest/include/gtest/gtest.h) + add_subdirectory(unittests) + set(${tests_added} ON PARENT_SCOPE) + else() + message(WARNING "gtest not found, unittests will not be available") + endif() +endfunction()