diff --git a/lld/CMakeLists.txt b/lld/CMakeLists.txt index 80e25204a65ee..b12e7bb74c6c5 100644 --- a/lld/CMakeLists.txt +++ b/lld/CMakeLists.txt @@ -177,6 +177,14 @@ if (LLD_DEFAULT_LD_LLD_IS_MINGW) add_definitions("-DLLD_DEFAULT_LD_LLD_IS_MINGW=1") endif() +option(LLD_LINK_GPL3 "Allow LLD to link to GPLv3-licensed plugins, e.g. liblto_plugin.so") +option(LLD_ENABLE_GNU_LTO "Enable support for GNU LTO plugin") +if(!LLD_LINK_GPL3) + # Support for GNU LTO require linking to liblto_plugin.so from GCC which is + # licensed as GPLv3. + set(LLD_ENABLE_GNU_LTO FALSE) +endif() + if (MSVC) add_definitions(-wd4530) # Suppress 'warning C4530: C++ exception handler used, but unwind semantics are not enabled.' add_definitions(-wd4062) # Suppress 'warning C4062: enumerator X in switch of enum Y is not handled' from system header. diff --git a/lld/ELF/CMakeLists.txt b/lld/ELF/CMakeLists.txt index ec3f6382282b1..82121cb6b2422 100644 --- a/lld/ELF/CMakeLists.txt +++ b/lld/ELF/CMakeLists.txt @@ -18,6 +18,15 @@ if(LLVM_ENABLE_ZSTD) list(APPEND imported_libs ${zstd_target}) endif() +set(GNULTO_INCLUDE_DIR "" CACHE PATH "Additional directory, where CMake should search for plugin-api.h") +if(LLD_ENABLE_GNU_LTO) + find_package(GNULTO REQUIRED) +endif() + +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/../include/lld/config.h.cmake + ${CMAKE_CURRENT_BINARY_DIR}/../include/lld/config.h) + add_lld_library(lldELF AArch64ErrataFix.cpp Arch/AArch64.cpp diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h index fd57967a1d21f..bdb7bcc097441 100644 --- a/lld/ELF/Config.h +++ b/lld/ELF/Config.h @@ -54,6 +54,7 @@ class TargetInfo; struct Ctx; struct Partition; struct PhdrEntry; +class IRCompiler; class BssSection; class GdbIndexSection; @@ -191,6 +192,7 @@ class LinkerDriver { void inferMachineType(); template void link(llvm::opt::InputArgList &args); template void compileBitcodeFiles(bool skipLinkedOutput); + template void compileGccIRFiles(bool skipLinkedOutput); bool tryAddFatLTOFile(MemoryBufferRef mb, StringRef archiveName, uint64_t offsetInArchive, bool lazy); // True if we are in --whole-archive and --no-whole-archive. @@ -199,7 +201,7 @@ class LinkerDriver { // True if we are in --start-lib and --end-lib. bool inLib = false; - std::unique_ptr lto; + std::unique_ptr lto; SmallVector, 0> files, ltoObjectFiles; public: @@ -241,9 +243,12 @@ struct Config { llvm::StringRef optRemarksPasses; llvm::StringRef optRemarksFormat; llvm::StringRef optStatsFilename; + llvm::StringRef plugin; + llvm::SmallVector pluginOpt; llvm::StringRef progName; llvm::StringRef printArchiveStats; llvm::StringRef printSymbolOrder; + llvm::StringRef resolutionFile; llvm::StringRef soName; llvm::StringRef sysroot; llvm::StringRef thinLTOCacheDir; diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp index 1beab8d33f4ba..55cae162303fe 100644 --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -1792,6 +1792,15 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &args) { cl::ResetAllOptionOccurrences(); + // Ignore -plugin=LLVMgold.so because we don't need to load it. + StringRef v = args.getLastArgValue(OPT_plugin); + if (!v.empty() && !v.ends_with("LLVMgold.so")) { + if (!llvm::sys::fs::exists(v)) + ErrAlways(ctx) << "Cannot find plugin " << v; + else + ctx.arg.plugin = v; + } + // Parse LTO options. if (auto *arg = args.getLastArg(OPT_plugin_opt_mcpu_eq)) parseClangOption(ctx, ctx.saver.save("-mcpu=" + StringRef(arg->getValue())), @@ -1802,14 +1811,32 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &args) { arg->getSpelling()); // GCC collect2 passes -plugin-opt=path/to/lto-wrapper with an absolute or - // relative path. Just ignore. If not ended with "lto-wrapper" (or - // "lto-wrapper.exe" for GCC cross-compiled for Windows), consider it an - // unsupported LLVMgold.so option and error. + // relative path. If not ended with "lto-wrapper" (or "lto-wrapper.exe" for + // GCC cross-compiled for Windows), consider it an unsupported LLVMgold.so + // option and error. for (opt::Arg *arg : args.filtered(OPT_plugin_opt_eq)) { StringRef v(arg->getValue()); if (!v.ends_with("lto-wrapper") && !v.ends_with("lto-wrapper.exe")) ErrAlways(ctx) << arg->getSpelling() << ": unknown plugin option '" << arg->getValue() << "'"; + else if (!ctx.arg.plugin.empty()) +#if LLD_ENABLE_GNU_LTO + ctx.arg.pluginOpt.push_back(v.str()); +#else + ErrAlways(ctx) << arg->getSpelling() + << " : support for GNU LTO is disabled"; +#endif + } + + // Parse GCC collect2 options. + if (!ctx.arg.plugin.empty()) { +#if LLD_ENABLE_GNU_LTO + StringRef v = args.getLastArgValue(OPT_plugin_opt_fresolution); + if (!v.empty()) { + ctx.arg.resolutionFile = v; + ctx.arg.pluginOpt.push_back(std::string("-fresolution=" + v.str())); + } +#endif } ctx.arg.passPlugins = args::getStrings(args, OPT_load_pass_plugins); @@ -2689,7 +2716,7 @@ static void markBuffersAsDontNeed(Ctx &ctx, bool skipLinkedOutput) { } // This function is where all the optimizations of link-time -// optimization takes place. When LTO is in use, some input files are +// optimization takes place. When LLVM LTO is in use, some input files are // not in native object file format but in the LLVM bitcode format. // This function compiles bitcode files into a few big native files // using LLVM functions and replaces bitcode symbols with the results. @@ -2738,6 +2765,39 @@ void LinkerDriver::compileBitcodeFiles(bool skipLinkedOutput) { } } +#if LLD_ENABLE_GNU_LTO +template +void LinkerDriver::compileGccIRFiles(bool skipLinkedOutput) { + llvm::TimeTraceScope timeScope("LTO"); + // Compile files and replace symbols. + GccIRCompiler *c = GccIRCompiler::getInstance(ctx); + lto.reset(c); + + for (ELFFileBase *file : ctx.objectFiles) + c->add(*file); + + ltoObjectFiles = c->compile(); + for (auto &file : ltoObjectFiles) { + auto *obj = cast>(file.get()); + obj->parse(/*ignoreComdats=*/true); + + // For defined symbols in non-relocatable output, + // compute isExported and parse '@'. + if (!ctx.arg.relocatable) + for (Symbol *sym : obj->getGlobalSymbols()) { + if (!sym->isDefined()) + continue; + if (ctx.arg.exportDynamic && sym->computeBinding(ctx) != STB_LOCAL) + sym->isExported = true; + if (sym->hasVersionSuffix) + sym->parseSymbolVersion(ctx); + } + ctx.objectFiles.push_back(obj); + } + return; +} +#endif + // The --wrap option is a feature to rename symbols so that you can write // wrappers for existing functions. If you pass `--wrap=foo`, all // occurrences of symbol `foo` are resolved to `__wrap_foo` (so, you are @@ -3295,7 +3355,13 @@ template void LinkerDriver::link(opt::InputArgList &args) { // except a few linker-synthesized ones will be added to the symbol table. const size_t numObjsBeforeLTO = ctx.objectFiles.size(); const size_t numInputFilesBeforeLTO = ctx.driver.files.size(); - compileBitcodeFiles(skipLinkedOutput); + if (ctx.arg.plugin.empty()) { + compileBitcodeFiles(skipLinkedOutput); +#if LLD_ENABLE_GNU_LTO + } else { + compileGccIRFiles(skipLinkedOutput); +#endif + } // Symbol resolution finished. Report backward reference problems, // --print-archive-stats=, and --why-extract=. diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp index a5921feb18299..8b9b55c4a5d47 100644 --- a/lld/ELF/InputFiles.cpp +++ b/lld/ELF/InputFiles.cpp @@ -1845,8 +1845,8 @@ static bool dtltoAdjustMemberPathIfThinArchive(Ctx &ctx, StringRef archivePath, return true; } -BitcodeFile::BitcodeFile(Ctx &ctx, MemoryBufferRef mb, StringRef archiveName, - uint64_t offsetInArchive, bool lazy) +IRFile::IRFile(Ctx &ctx, MemoryBufferRef mb, StringRef archiveName, + uint64_t offsetInArchive, bool lazy) : InputFile(ctx, BitcodeKind, mb) { this->archiveName = archiveName; this->lazy = lazy; @@ -1958,7 +1958,7 @@ void BitcodeFile::parse() { addDependentLibrary(ctx, l, this); } -void BitcodeFile::parseLazy() { +void IRFile::parseLazy() { numSymbols = obj->symbols().size(); symbols = std::make_unique(numSymbols); for (auto [i, irSym] : llvm::enumerate(obj->symbols())) { diff --git a/lld/ELF/InputFiles.h b/lld/ELF/InputFiles.h index ba844ad18f637..6678be65f6942 100644 --- a/lld/ELF/InputFiles.h +++ b/lld/ELF/InputFiles.h @@ -16,6 +16,7 @@ #include "lld/Common/Reproduce.h" #include "llvm/ADT/DenseSet.h" #include "llvm/BinaryFormat/Magic.h" +#include "llvm/LTO/LTO.h" #include "llvm/Object/ELF.h" #include "llvm/Support/MemoryBufferRef.h" #include "llvm/Support/Threading.h" @@ -321,15 +322,25 @@ template class ObjFile : public ELFFileBase { ArrayRef shndxTable; }; -class BitcodeFile : public InputFile { +class IRFile : public InputFile { public: - BitcodeFile(Ctx &, MemoryBufferRef m, StringRef archiveName, - uint64_t offsetInArchive, bool lazy); + IRFile(Ctx &ctx, MemoryBufferRef m, StringRef archiveName, + uint64_t offsetInArchive, bool lazy); static bool classof(const InputFile *f) { return f->kind() == BitcodeKind; } - void parse(); + virtual void parse() = 0; void parseLazy(); - void postParse(); + virtual void postParse() = 0; std::unique_ptr obj; +}; + +class BitcodeFile : public IRFile { +public: + BitcodeFile(Ctx &ctx, MemoryBufferRef m, StringRef archiveName, + uint64_t offsetInArchive, bool lazy) + : IRFile(ctx, m, archiveName, offsetInArchive, lazy) {}; + static bool classof(const InputFile *f) { return f->kind() == BitcodeKind; } + void parse() override; + void postParse() override; std::vector keptComdats; }; diff --git a/lld/ELF/LTO.cpp b/lld/ELF/LTO.cpp index 8d4a6c9e3a81e..4068f04599834 100644 --- a/lld/ELF/LTO.cpp +++ b/lld/ELF/LTO.cpp @@ -26,6 +26,7 @@ #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include +#include #include #include #include @@ -165,7 +166,7 @@ static lto::Config createConfig(Ctx &ctx) { return c; } -BitcodeCompiler::BitcodeCompiler(Ctx &ctx) : ctx(ctx) { +BitcodeCompiler::BitcodeCompiler(Ctx &ctx) : IRCompiler(ctx) { // Initialize indexFile. if (!ctx.arg.thinLTOIndexOnlyArg.empty()) indexFile = openFile(ctx.arg.thinLTOIndexOnlyArg); @@ -215,9 +216,7 @@ BitcodeCompiler::BitcodeCompiler(Ctx &ctx) : ctx(ctx) { } } -BitcodeCompiler::~BitcodeCompiler() = default; - -void BitcodeCompiler::add(BitcodeFile &f) { +void IRCompiler::add(IRFile &f) { lto::InputFile &obj = *f.obj; bool isExec = !ctx.arg.shared && !ctx.arg.relocatable; @@ -278,7 +277,7 @@ void BitcodeCompiler::add(BitcodeFile &f) { // their values are still not final. r.LinkerRedefined = sym->scriptDefined; } - checkError(ctx.e, ltoObj->add(std::move(f.obj), resols)); + addObject(f, resols); } // If LazyObjFile has not been added to link, emit empty index files. @@ -421,3 +420,242 @@ SmallVector, 0> BitcodeCompiler::compile() { } return ret; } + +void BitcodeCompiler::addObject(IRFile &f, + std::vector &r) { + checkError(ctx.e, ltoObj->add(std::move(f.obj), r)); +} + +#if LLD_ENABLE_GNU_LTO +GccIRCompiler *GccIRCompiler::singleton = nullptr; + +GccIRCompiler *GccIRCompiler::getInstance() { + assert(singleton != nullptr); + return singleton; +} + +GccIRCompiler *GccIRCompiler::getInstance(Ctx &ctx) { + if (singleton == nullptr) { + singleton = new GccIRCompiler(ctx); + singleton->loadPlugin(); + } + + return singleton; +} + +GccIRCompiler::GccIRCompiler(Ctx &ctx) : IRCompiler(ctx) { + singleton = nullptr; + + // TODO: Properly find the right size. + int tvsz = 100; + tv = new ld_plugin_tv[tvsz]; + initializeTv(); +} + +GccIRCompiler::~GccIRCompiler() { + singleton = nullptr; + delete[] tv; +} + +void GccIRCompiler::loadPlugin() { + std::string Error; + plugin = llvm::sys::DynamicLibrary::getPermanentLibrary(ctx.arg.plugin.data(), + &Error); + if (!plugin.isValid()) { + error(Error); + return; + } + void *tmp = plugin.getAddressOfSymbol("onload"); + if (!tmp) { + error("Plugin does not provide onload()"); + return; + } + + ld_plugin_onload onload; + // Ensure source and destination types have the same size. + assert(sizeof(ld_plugin_onload) == sizeof(void *)); + std::memcpy(&onload, &tmp, sizeof(ld_plugin_onload)); + + (*onload)(tv); +} + +enum ld_plugin_status regClaimFile(ld_plugin_claim_file_handler handler) { + GccIRCompiler *c = GccIRCompiler::getInstance(); + return c->registerClaimFile(handler); +} + +enum ld_plugin_status +GccIRCompiler::registerClaimFile(ld_plugin_claim_file_handler handler) { + claimFileHandler = handler; + return LDPS_OK; +} + +#if HAVE_LDPT_REGISTER_CLAIM_FILE_HOOK_V2 +enum ld_plugin_status regClaimFileV2(ld_plugin_claim_file_handler handler) { + GccIRCompiler *c = GccIRCompiler::getInstance(); + return c->registerClaimFileV2(handler); +} + +enum ld_plugin_status +GccIRCompiler::registerClaimFileV2(ld_plugin_claim_file_handler_v2 handler) { + claimFileHandlerV2 = handler; + return LDPS_OK; +} +#endif + +enum ld_plugin_status +regAllSymbolsRead(ld_plugin_all_symbols_read_handler handler) { + GccIRCompiler *c = GccIRCompiler::getInstance(); + return c->registerAllSymbolsRead(handler); +} + +enum ld_plugin_status GccIRCompiler::registerAllSymbolsRead( + ld_plugin_all_symbols_read_handler handler) { + allSymbolsReadHandler = handler; + return LDPS_OK; +} + +static enum ld_plugin_status addSymbols(void *handle, int nsyms, + const struct ld_plugin_symbol *syms) { + ELFFileBase *f = (ELFFileBase *)handle; + if (f == NULL) + return LDPS_ERR; + + for (int i = 0; i < nsyms; i++) { + // TODO: Add symbols. + // TODO: Convert these symbosl into ArrayRef and + // ArrayRef ? + } + + return LDPS_OK; +} + +static enum ld_plugin_status getSymbols(const void *handle, int nsyms, + struct ld_plugin_symbol *syms) { + for (int i = 0; i < nsyms; i++) { + syms[i].resolution = LDPR_UNDEF; + // TODO: Implement other scenarios. + } + return LDPS_OK; +} + +ld_plugin_status addInputFile(const char *pathname) { + GccIRCompiler *c = GccIRCompiler::getInstance(); + + if (c->addCompiledFile(StringRef(pathname))) + return LDPS_OK; + else + return LDPS_ERR; +} + +void GccIRCompiler::initializeTv() { + int i = 0; + +#define TVU_SETTAG(t, f, v) \ + { \ + tv[i].tv_tag = t; \ + tv[i].tv_u.tv_##f = v; \ + i++; \ + } + + TVU_SETTAG(LDPT_MESSAGE, message, message); + TVU_SETTAG(LDPT_API_VERSION, val, LD_PLUGIN_API_VERSION); + for (std::string &s : ctx.arg.pluginOpt) { + TVU_SETTAG(LDPT_OPTION, string, s.c_str()); + } + ld_plugin_output_file_type o; + if (ctx.arg.pie) + o = LDPO_PIE; + else if (ctx.arg.relocatable) + o = LDPO_REL; + else if (ctx.arg.shared) + o = LDPO_DYN; + else + o = LDPO_EXEC; + TVU_SETTAG(LDPT_LINKER_OUTPUT, val, o); + TVU_SETTAG(LDPT_OUTPUT_NAME, string, ctx.arg.outputFile.data()); + // Share the address of a C wrapper that is API-compatible with + // plugin-api.h. + TVU_SETTAG(LDPT_REGISTER_CLAIM_FILE_HOOK, register_claim_file, regClaimFile); +#if HAVE_LDPT_REGISTER_CLAIM_FILE_HOOK_V2 + TVU_SETTAG(LDPT_REGISTER_CLAIM_FILE_HOOK_V2, register_claim_file_v2, + regClaimFileV2); +#endif + + TVU_SETTAG(LDPT_ADD_SYMBOLS, add_symbols, addSymbols); + TVU_SETTAG(LDPT_REGISTER_ALL_SYMBOLS_READ_HOOK, register_all_symbols_read, + regAllSymbolsRead); + TVU_SETTAG(LDPT_GET_SYMBOLS, get_symbols, getSymbols); + TVU_SETTAG(LDPT_ADD_INPUT_FILE, add_input_file, addInputFile); +} + +void GccIRCompiler::add(ELFFileBase &f) { + struct ld_plugin_input_file file; + + std::string name = f.getName().str(); + file.name = f.getName().data(); + file.handle = const_cast(reinterpret_cast(&f)); + + std::error_code ec = sys::fs::openFileForRead(name, file.fd); + if (ec) { + error("Cannot open file " + name + ": " + ec.message()); + return; + } + file.offset = 0; + uint64_t size; + ec = sys::fs::file_size(name, size); + if (ec) { + error("Cannot get the size of file " + name + ": " + ec.message()); + sys::fs::closeFile(file.fd); + return; + } + if (size > 0 && size <= INT_MAX) + file.filesize = size; + + int claimed; +#if HAVE_LDPT_REGISTER_CLAIM_FILE_HOOK_V2 + ld_plugin_status status = claimFileHandler(&file, &claimed, 1); +#else + ld_plugin_status status = claimFileHandler(&file, &claimed); +#endif + + if (status != LDPS_OK) + error("liblto returned " + std::to_string(status)); + + ec = sys::fs::closeFile(file.fd); + if (ec) { + error(ec.message()); + } +} + +SmallVector, 0> GccIRCompiler::compile() { + SmallVector, 0> ret; + ld_plugin_status status = allSymbolsReadHandler(); + if (status != LDPS_OK) + error("The plugin returned an error after all symbols were read."); + + for (auto &m : files) { + ret.push_back(createObjFile(ctx, m->getMemBufferRef())); + } + return ret; +} + +void GccIRCompiler::addObject(IRFile &f, + std::vector &r) { + // TODO: Implement this. +} + +enum ld_plugin_status GccIRCompiler::message(int level, const char *format, + ...) { + // TODO: Implement this function. + return LDPS_OK; +} + +bool GccIRCompiler::addCompiledFile(StringRef path) { + std::optional mbref = readFile(ctx, path); + if (!mbref) + return false; + files.push_back(MemoryBuffer::getMemBuffer(*mbref)); + return true; +} +#endif diff --git a/lld/ELF/LTO.h b/lld/ELF/LTO.h index acf3bcff7f2f1..36e37b1c60e34 100644 --- a/lld/ELF/LTO.h +++ b/lld/ELF/LTO.h @@ -21,40 +21,106 @@ #define LLD_ELF_LTO_H #include "lld/Common/LLVM.h" +#include "lld/config.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/SmallString.h" +#include "llvm/Support/DynamicLibrary.h" #include "llvm/Support/raw_ostream.h" #include #include namespace llvm::lto { class LTO; +struct SymbolResolution; } namespace lld::elf { struct Ctx; class BitcodeFile; +class ELFFileBase; class InputFile; +class IRFile; +class BinaryFile; + +class IRCompiler { +protected: + Ctx &ctx; + llvm::DenseSet thinIndices; + llvm::DenseSet usedStartStop; + virtual void addObject(IRFile &f, + std::vector &r) = 0; + +public: + IRCompiler(Ctx &ctx) : ctx(ctx) {} + virtual ~IRCompiler() {}; + void add(IRFile &f); + virtual SmallVector, 0> compile() = 0; +}; + +class BitcodeCompiler : public IRCompiler { +protected: + void addObject(IRFile &f, + std::vector &r) override; -class BitcodeCompiler { public: BitcodeCompiler(Ctx &ctx); - ~BitcodeCompiler(); + ~BitcodeCompiler() {}; - void add(BitcodeFile &f); - SmallVector, 0> compile(); + void add(BinaryFile &f); + SmallVector, 0> compile() override; private: - Ctx &ctx; std::unique_ptr ltoObj; // An array of (module name, native relocatable file content) pairs. SmallVector>, 0> buf; std::vector> files; SmallVector filenames; - llvm::DenseSet usedStartStop; std::unique_ptr indexFile; - llvm::DenseSet thinIndices; }; + +#if LLD_ENABLE_GNU_LTO +#include + +class GccIRCompiler : public IRCompiler { +protected: + void addObject(IRFile &f, + std::vector &r) override; + +public: + ~GccIRCompiler(); + static GccIRCompiler *getInstance(); + static GccIRCompiler *getInstance(Ctx &ctx); + + void add(ELFFileBase &f); + SmallVector, 0> compile() override; + static enum ld_plugin_status message(int level, const char *format, ...); + enum ld_plugin_status registerClaimFile(ld_plugin_claim_file_handler handler); +#if HAVE_LDPT_REGISTER_CLAIM_FILE_HOOK_V2 + enum ld_plugin_status + registerClaimFileV2(ld_plugin_claim_file_handler_v2 handler); +#endif + enum ld_plugin_status + registerAllSymbolsRead(ld_plugin_all_symbols_read_handler handler); + void loadPlugin(); + bool addCompiledFile(StringRef path); + +private: + GccIRCompiler(Ctx &ctx); + std::vector> files; + static GccIRCompiler *singleton; + struct ld_plugin_tv *tv; + ld_plugin_claim_file_handler claimFileHandler; +#if HAVE_LDPT_REGISTER_CLAIM_FILE_HOOK_V2 + ld_plugin_claim_file_handler_v2 claimFileHandlerV2; +#endif + ld_plugin_all_symbols_read_handler allSymbolsReadHandler; + // Handle for the shared library created via dlopen(). + llvm::sys::DynamicLibrary plugin; + + void initializeTv(); +}; +#endif + } // namespace lld::elf #endif diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td index f0523185a0a31..6fe16fda08a4e 100644 --- a/lld/ELF/Options.td +++ b/lld/ELF/Options.td @@ -796,15 +796,15 @@ def: J<"plugin-opt=thinlto-prefix-replace=">, // just ignore the option on lld side as it's easier. In fact, the linker could // be called 'ld' and understanding which linker is used would require parsing of // --version output. -defm plugin: Eq<"plugin", "Ignored for compatibility with GNU linkers">; +defm plugin: Eq<"plugin", "Use a plugin in the linking process">; def plugin_opt_eq_minus: J<"plugin-opt=-">, HelpText<"Specify an LLVM option for compatibility with LLVMgold.so">; def: J<"plugin-opt=thinlto">; -// Ignore GCC collect2 LTO plugin related options. Note that we don't support -// GCC LTO, but GCC collect2 passes these options even in non-LTO mode. -def: J<"plugin-opt=-fresolution=">; +// GCC collect2 LTO plugin related options. +def plugin_opt_fresolution: J<"plugin-opt=-fresolution=">; +// Ignore -pass-through because it is unnecessary. def: J<"plugin-opt=-pass-through=">; // This may be either an unhandled LLVMgold.so feature or GCC passed // -plugin-opt=path/to/{liblto_plugin.so,lto-wrapper} diff --git a/lld/cmake/modules/FindGNULTO.cmake b/lld/cmake/modules/FindGNULTO.cmake new file mode 100644 index 0000000000000..2ba603bf489e2 --- /dev/null +++ b/lld/cmake/modules/FindGNULTO.cmake @@ -0,0 +1,38 @@ +# Attempts to discover GCC plugin API. +# +# Example usage: +# +# find_package(GNULTO) +# +# If successful, the following variables will be defined: +# HAVE_GNULTO_H +# +# HAVE_LDPT_REGISTER_CLAIM_FILE_HOOK_V2_SRC is defined depending on the +# features available in plugin-api.h. + +find_path(GNULTO_INCLUDE_DIRS plugin-api.h PATHS ${GNULTO_INCLUDE_DIR}) +if( EXISTS "${GNULTO_INCLUDE_DIRS}/plugin-api.h" ) + set(HAVE_GNULTO_H 1 CACHE INTERNAL "") + + include(CMakePushCheckState) + cmake_push_check_state() + set(HAVE_LDPT_REGISTER_CLAIM_FILE_HOOK_V2_SRC [=[ + #include + #include + #include + void test(struct ld_plugin_tv *tv) { + tv->tv_register_claim_file_v2 = NULL; + } + ]=]) + if(DEFINED CMAKE_C_COMPILER) + include(CheckCSourceCompiles) + check_c_source_compiles("${HAVE_LDPT_REGISTER_CLAIM_FILE_HOOK_V2_SRC}" HAVE_LDPT_REGISTER_CLAIM_FILE_HOOK_V2) + else() + include(CheckCXXSourceCompiles) + check_cxx_source_compiles("${HAVE_LDPT_REGISTER_CLAIM_FILE_HOOK_V2_SRC}" HAVE_LDPT_REGISTER_CLAIM_FILE_HOOK_V2) + endif() + cmake_pop_check_state() +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(GNULTO DEFAULT_MSG) diff --git a/lld/include/lld/config.h.cmake b/lld/include/lld/config.h.cmake new file mode 100644 index 0000000000000..4eef0e1ed7c93 --- /dev/null +++ b/lld/include/lld/config.h.cmake @@ -0,0 +1,16 @@ +#ifndef CONFIG_H +#define CONFIG_H + +// Include this header only under the lld source tree. +// This is a private header. + +/* Allow LLD to link to GPLv3-licensed files. */ +#cmakedefine01 LLD_LINK_GPL3 + +/* Enable support for GNU LTO Format, i.e. use LLD to link GCC LTO files. */ +#cmakedefine01 LLD_ENABLE_GNU_LTO + +/* Define to 1 if plugin-api.h supports tv_register_claim_file_v2, and to 0 otherwise. */ +#cmakedefine01 HAVE_LDPT_REGISTER_CLAIM_FILE_HOOK_V2 + +#endif diff --git a/lld/test/ELF/Inputs/plugin.so b/lld/test/ELF/Inputs/plugin.so new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/lld/test/ELF/gnu-lto/hello.test b/lld/test/ELF/gnu-lto/hello.test new file mode 100644 index 0000000000000..273039284ddc4 --- /dev/null +++ b/lld/test/ELF/gnu-lto/hello.test @@ -0,0 +1,28 @@ +// REQUIRES: gnu_lto, gcc +// Test if we can link a simple LTO'ed program. +// Ensure that LLD was used by checking for the output from LLD_VERSION. + +// RUN: gcc -c -flto -x c %s -o %t.dynamic.o +// RUN: gcc -fuse-ld=lld -flto %t.dynamic.o -o %t.dynamic +// RUN: %t.dynamic | FileCheck %s +// RUN: llvm-objdump -t -j .comment -s %t.dynamic | \ +// RUN: FileCheck --check-prefix=OBJDUMP %s + +// RUN: gcc -c -flto -x c -fPIE %s -o %t.pie.o +// RUN: gcc -fuse-ld=lld -flto -pie %t.pie.o -o %t.pie +// RUN: %t.pie | FileCheck %s +// RUN: llvm-objdump -t -j .comment -s %t.pie \ +// RUN: | FileCheck --check-prefix=OBJDUMP %s + +#include + +int main() { + printf("It works!\n"); + return 0; +} + +// CHECK: It works! + +// OBJDUMP: __gnu_lto +// OBJDUMP: .LLD 1.0 +// OBJDUMP: GCC diff --git a/lld/test/ELF/ignore-plugin.test b/lld/test/ELF/ignore-plugin.test deleted file mode 100644 index fcd3fa64195c4..0000000000000 --- a/lld/test/ELF/ignore-plugin.test +++ /dev/null @@ -1,2 +0,0 @@ -RUN: not ld.lld --plugin foo 2>&1 | FileCheck %s -CHECK: no input files diff --git a/lld/test/ELF/lto-plugin-ignore.s b/lld/test/ELF/lto-plugin-ignore.s index dd39139b62439..a105e61f88823 100644 --- a/lld/test/ELF/lto-plugin-ignore.s +++ b/lld/test/ELF/lto-plugin-ignore.s @@ -6,7 +6,6 @@ ## GCC collect2 passes several LTO related options to the linker even if -flto is not used. ## We need to ignore them. Note that the lto-wrapper path can be relative. # RUN: ld.lld %t.o -o /dev/null \ -# RUN: -plugin path/to/liblto_plugin.so \ # RUN: -plugin-opt=/path/to/lto-wrapper \ # RUN: -plugin-opt=/path/to/lto-wrapper.exe \ # RUN: -plugin-opt=relative/path/to/lto-wrapper \ diff --git a/lld/test/ELF/plugin.test b/lld/test/ELF/plugin.test new file mode 100644 index 0000000000000..25f9568019e58 --- /dev/null +++ b/lld/test/ELF/plugin.test @@ -0,0 +1,11 @@ +RUN: not ld.lld -plugin %S/Inputs/plugin.so 2>&1 | FileCheck %s +RUN: not ld.lld --plugin=%S/Inputs/plugin.so 2>&1 | FileCheck %s + +CHECK: no input files +CHECK-NOT: unknown argument + +RUN: not ld.lld -plugin foo 2>&1 | FileCheck --check-prefix=MISSING %s +RUN: not ld.lld --plugin=foo 2>&1 | FileCheck --check-prefix=MISSING %s + +MISSING: Cannot find plugin foo +MISSING-NOT: unknown argument diff --git a/lld/test/lit.cfg.py b/lld/test/lit.cfg.py index 336945729954e..38bbc28aa1d5d 100644 --- a/lld/test/lit.cfg.py +++ b/lld/test/lit.cfg.py @@ -182,3 +182,25 @@ # ELF tests expect the default target for ld.lld to be ELF. if config.ld_lld_default_mingw: config.excludes.append("ELF") + + +def prepend_path(path): + target_arch = getattr(config, "target_arch", None) + name = "PATH" + if config.operating_system == "Windows": + sep = ";" + else: + sep = ":" + if name in config.environment: + config.environment[name] = path + sep + config.environment[name] + else: + config.environment[name] = path + + +gcc_executable = lit.util.which("gcc", config.environment["PATH"]) +if gcc_executable: + config.available_features.add("gcc") + +if config.has_gnu_lto: + prepend_path(config.llvm_obj_root) + config.available_features.add("gnu_lto") diff --git a/lld/test/lit.site.cfg.py.in b/lld/test/lit.site.cfg.py.in index bb99976005543..847dfc1853342 100644 --- a/lld/test/lit.site.cfg.py.in +++ b/lld/test/lit.site.cfg.py.in @@ -26,6 +26,8 @@ config.ld_lld_default_mingw = @LLD_DEFAULT_LD_LLD_IS_MINGW@ config.build_examples = @LLVM_BUILD_EXAMPLES@ config.has_plugins = @LLVM_ENABLE_PLUGINS@ config.linked_bye_extension = @LLVM_BYE_LINK_INTO_TOOLS@ +config.has_gnu_lto = lit.util.pythonize_bool("@LLD_ENABLE_GNU_LTO@") +config.operating_system = "@CMAKE_SYSTEM_NAME@" import lit.llvm lit.llvm.initialize(lit_config, config)