diff --git a/lld/CMakeLists.txt b/lld/CMakeLists.txt --- a/lld/CMakeLists.txt +++ b/lld/CMakeLists.txt @@ -204,6 +204,12 @@ add_subdirectory(tools/lld) if (LLVM_INCLUDE_TESTS) + add_custom_target(LLDUnitTests) + if (EXISTS ${LLVM_THIRD_PARTY_DIR}/unittest/googletest/include/gtest/gtest.h) + add_subdirectory(unittests) + else() + message(WARNING "gtest not found, unittests will not be available") + endif() add_subdirectory(test) endif() diff --git a/lld/COFF/CMakeLists.txt b/lld/COFF/CMakeLists.txt --- a/lld/COFF/CMakeLists.txt +++ b/lld/COFF/CMakeLists.txt @@ -43,7 +43,6 @@ WindowsManifest LINK_LIBS - lldCommon ${LLVM_PTHREAD_LIB} ${LLVM_ATOMIC_LIB} diff --git a/lld/Common/CMakeLists.txt b/lld/Common/CMakeLists.txt --- a/lld/Common/CMakeLists.txt +++ b/lld/Common/CMakeLists.txt @@ -23,9 +23,15 @@ add_lld_library(lldCommon Args.cpp CommonLinkerContext.cpp + DefaultDrivers/DefaultFunctions_COFF.cpp + DefaultDrivers/DefaultFunctions_ELF.cpp + DefaultDrivers/DefaultFunctions_MachO.cpp + DefaultDrivers/DefaultFunctions_MinGW.cpp + DefaultDrivers/DefaultFunctions_wasm.cpp DWARF.cpp ErrorHandler.cpp Filesystem.cpp + LLDLibrary.cpp Memory.cpp Reproduce.cpp Strings.cpp diff --git a/lld/Common/DefaultDrivers/DefaultFunctions_COFF.cpp b/lld/Common/DefaultDrivers/DefaultFunctions_COFF.cpp new file mode 100644 --- /dev/null +++ b/lld/Common/DefaultDrivers/DefaultFunctions_COFF.cpp @@ -0,0 +1,23 @@ +//===- DefaultFunctions.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 "llvm/ADT/ArrayRef.h" +#include "llvm/Support/raw_ostream.h" + +namespace lld { +// Provide empty implementations for all drivers, to allow on-demand usage for +// any of them. For example, users of the LLD library mode can use only the ELF +// driver, by only linking lldELF in their target executable. +namespace coff { +bool link(llvm::ArrayRef args, llvm::raw_ostream &stdoutOS, + llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput) { + llvm::errs() << "The LLD COFF driver is not available in this build.\n"; + return false; +} +} // namespace coff +} // namespace lld diff --git a/lld/Common/DefaultDrivers/DefaultFunctions_ELF.cpp b/lld/Common/DefaultDrivers/DefaultFunctions_ELF.cpp new file mode 100644 --- /dev/null +++ b/lld/Common/DefaultDrivers/DefaultFunctions_ELF.cpp @@ -0,0 +1,23 @@ +//===- DefaultFunctions.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 "llvm/ADT/ArrayRef.h" +#include "llvm/Support/raw_ostream.h" + +namespace lld { +// Provide empty implementations for all drivers, to allow on-demand usage for +// any of them. For example, users of the LLD library mode can use only the ELF +// driver, by only linking lldELF in their target executable. +namespace elf { +bool link(llvm::ArrayRef args, llvm::raw_ostream &stdoutOS, + llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput) { + llvm::errs() << "The LLD ELF driver is not available in this build.\n"; + return false; +} +} // namespace elf +} // namespace lld diff --git a/lld/Common/DefaultDrivers/DefaultFunctions_MachO.cpp b/lld/Common/DefaultDrivers/DefaultFunctions_MachO.cpp new file mode 100644 --- /dev/null +++ b/lld/Common/DefaultDrivers/DefaultFunctions_MachO.cpp @@ -0,0 +1,23 @@ +//===- DefaultFunctions.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 "llvm/ADT/ArrayRef.h" +#include "llvm/Support/raw_ostream.h" + +namespace lld { +// Provide empty implementations for all drivers, to allow on-demand usage for +// any of them. For example, users of the LLD library mode can use only the ELF +// driver, by only linking lldELF in their target executable. +namespace macho { +bool link(llvm::ArrayRef args, llvm::raw_ostream &stdoutOS, + llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput) { + llvm::errs() << "The LLD Mach-O driver is not available in this build.\n"; + return false; +} +} // namespace macho +} // namespace lld diff --git a/lld/Common/DefaultDrivers/DefaultFunctions_MinGW.cpp b/lld/Common/DefaultDrivers/DefaultFunctions_MinGW.cpp new file mode 100644 --- /dev/null +++ b/lld/Common/DefaultDrivers/DefaultFunctions_MinGW.cpp @@ -0,0 +1,23 @@ +//===- DefaultFunctions.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 "llvm/ADT/ArrayRef.h" +#include "llvm/Support/raw_ostream.h" + +namespace lld { +// Provide empty implementations for all drivers, to allow on-demand usage for +// any of them. For example, users of the LLD library mode can use only the ELF +// driver, by only linking lldELF in their target executable. +namespace mingw { +bool link(llvm::ArrayRef args, llvm::raw_ostream &stdoutOS, + llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput) { + llvm::errs() << "The LLD MinGW driver is not available in this build.\n"; + return false; +} +} // namespace mingw +} // namespace lld diff --git a/lld/Common/DefaultDrivers/DefaultFunctions_wasm.cpp b/lld/Common/DefaultDrivers/DefaultFunctions_wasm.cpp new file mode 100644 --- /dev/null +++ b/lld/Common/DefaultDrivers/DefaultFunctions_wasm.cpp @@ -0,0 +1,23 @@ +//===- DefaultFunctions.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 "llvm/ADT/ArrayRef.h" +#include "llvm/Support/raw_ostream.h" + +namespace lld { +// Provide empty implementations for all drivers, to allow on-demand usage for +// any of them. For example, users of the LLD library mode can use only the ELF +// driver, by only linking lldELF in their target executable. +namespace wasm { +bool link(llvm::ArrayRef args, llvm::raw_ostream &stdoutOS, + llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput) { + llvm::errs() << "The LLD Wasm driver is not available in this build.\n"; + return false; +} +} // namespace wasm +} // namespace lld diff --git a/lld/Common/LLDLibrary.cpp b/lld/Common/LLDLibrary.cpp new file mode 100644 --- /dev/null +++ b/lld/Common/LLDLibrary.cpp @@ -0,0 +1,216 @@ +//===- 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; + +enum Flavor { + Invalid, + Gnu, // -flavor gnu + MinGW, // -flavor gnu MinGW + WinLink, // -flavor link + Darwin, // -flavor darwin + Wasm, // -flavor wasm +}; + +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(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 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(std::vector &v) { + // Parse -flavor option. + if (v.size() > 1 && v[1] == StringRef("-flavor")) { + if (v.size() <= 2) { + err("missing arg value for '-flavor'"); + return Invalid; + } + Flavor f = getFlavor(v[2]); + if (f == Invalid) { + err("Unknown flavor: " + StringRef(v[2])); + return Invalid; + } + 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); + 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(std::vector &v) { + Flavor f = parseFlavorWithoutMinGW(v); + if (f == Gnu) { + auto isPE = isPETarget(v); + if (!isPE) + return Invalid; + if (*isPE) + return MinGW; + } + return f; +} + +static bool invalidLink(llvm::ArrayRef args, + llvm::raw_ostream &stdoutOS, + llvm::raw_ostream &stderrOS, bool exitEarly, + bool disableOutput) { + return false; +} + +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. +int lldMain(std::vector &args, llvm::raw_ostream &stdoutOS, + llvm::raw_ostream &stderrOS, bool exitEarly) { + Flavor f = parseFlavor(args); + auto link = [&]() { + if (f == MinGW) + 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 + return &invalidLink; + }(); + + // Run the driver. If an error occurs, false will be returned. + int r = !link(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; +} + +// Similar to lldMain except that exceptions are caught. +SafeReturn safeLldMain(std::vector &args, + 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(args, 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 diff --git a/lld/ELF/CMakeLists.txt b/lld/ELF/CMakeLists.txt --- a/lld/ELF/CMakeLists.txt +++ b/lld/ELF/CMakeLists.txt @@ -74,7 +74,6 @@ TargetParser LINK_LIBS - lldCommon ${imported_libs} ${LLVM_PTHREAD_LIB} diff --git a/lld/MachO/CMakeLists.txt b/lld/MachO/CMakeLists.txt --- a/lld/MachO/CMakeLists.txt +++ b/lld/MachO/CMakeLists.txt @@ -52,7 +52,6 @@ TextAPI LINK_LIBS - lldCommon ${LLVM_PTHREAD_LIB} ${XAR_LIB} diff --git a/lld/MinGW/CMakeLists.txt b/lld/MinGW/CMakeLists.txt --- a/lld/MinGW/CMakeLists.txt +++ b/lld/MinGW/CMakeLists.txt @@ -12,7 +12,6 @@ LINK_LIBS lldCOFF - lldCommon DEPENDS MinGWOptionsTableGen 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 @@ -24,7 +24,8 @@ // 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, +SafeReturn safeLldMain(std::vector &args, + llvm::raw_ostream &stdoutOS, llvm::raw_ostream &stderrOS); namespace coff { 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/CMakeLists.txt b/lld/tools/lld/CMakeLists.txt --- a/lld/tools/lld/CMakeLists.txt +++ b/lld/tools/lld/CMakeLists.txt @@ -20,12 +20,12 @@ lld_target_link_libraries(lld PRIVATE - lldCommon lldCOFF lldELF lldMachO lldMinGW lldWasm + lldCommon # put this last, to avoid importing the default symbols for lld-as-library usage. ) install(TARGETS lld 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,12 @@ using namespace llvm; using namespace llvm::sys; -enum Flavor { - Invalid, - Gnu, // -flavor gnu - WinLink, // -flavor link - Darwin, // -flavor darwin - Wasm, // -flavor wasm -}; +namespace lld { +extern bool inTestOutputDisabled; -[[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}; -} +int lldMain(std::vector &args, llvm::raw_ostream &stdoutOS, + llvm::raw_ostream &stderrOS, bool exitEarly); +} // 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 @@ -224,11 +74,12 @@ LLVM_BUILTIN_TRAP; } + std::vector 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()); + return lldMain(args, llvm::outs(), llvm::errs(), /*exitEarly=*/true); std::optional mainRet; CrashRecoveryContext::Enable(); @@ -238,8 +89,7 @@ inTestOutputDisabled = (i != 1); // Execute one iteration. - auto r = safeLldMain(argc, const_cast(argv), llvm::outs(), - llvm::errs()); + auto r = safeLldMain(args, llvm::outs(), llvm::errs()); if (!r.canRunAgain) exitLld(r.ret); // Exit now, can't re-execute again. 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,24 @@ +//===- 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" + +bool lldInvoke(std::vector args) { + args.push_back("--version"); + lld::SafeReturn s = lld::safeLldMain(args, llvm::outs(), llvm::errs()); + return !s.ret && s.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 + lldCOFF + lldELF + lldMachO + lldMinGW + lldWasm + lldCommon # add this last, to avoid including the default symbols +) 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 + lldELF + lldCommon # add this last, to avoid including the default symbols +) 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; +} + +bool lldInvoke(const char *inPath, const char *outPath) { + std::vector args{"ld.lld", "-shared", inPath, "-o", outPath}; + lld::SafeReturn s = lld::safeLldMain(args, llvm::outs(), llvm::errs()); + return !s.ret && 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,24 @@ +//===- 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" + +bool lldInvoke(const char *lldExe) { + std::vector args{lldExe, "--version"}; + lld::SafeReturn s = lld::safeLldMain(args, llvm::outs(), llvm::errs()); + return !s.ret && 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")); // ELF + 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/CMakeLists.txt b/lld/wasm/CMakeLists.txt --- a/lld/wasm/CMakeLists.txt +++ b/lld/wasm/CMakeLists.txt @@ -31,9 +31,6 @@ Support TargetParser - LINK_LIBS - lldCommon - DEPENDS WasmOptionsTableGen intrinsics_gen