| // Copyright 2021 The IREE Authors |
| // |
| // 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 "compiler/plugins/target/LLVMCPU/LinkerTool.h" |
| #include "iree/compiler/Utils/ToolUtils.h" |
| #include "llvm/ADT/StringExtras.h" |
| #include "llvm/IR/Function.h" |
| #include "llvm/IR/IRBuilder.h" |
| #include "llvm/Support/FileSystem.h" |
| #include "llvm/Support/FormatVariadic.h" |
| |
| #define DEBUG_TYPE "llvm-linker" |
| |
| namespace mlir::iree_compiler::IREE::HAL { |
| |
| // Embedded ELF linker targeting IREE's ELF loader (or Android/Linux). |
| // This uses lld exclusively (though it can be overridden) as that lets us |
| // ensure we are consistently generating ELFs such that they can be used |
| // across our target platforms and with our loader. |
| // |
| // For consistency we follow the Linux ABI rules on all architectures and |
| // limit what we allow: |
| // - Triples of the form "{arch}-pc-linux-elf" only. |
| // - No builtin libraries are available. |
| // - Extra GNU-style symbol lookups are disabled (sysv only) to save binary |
| // size. The loader does not use any hash tables but .hash is mandatory in |
| // the spec and included for compatibility. |
| // - No lazy binding; all symbols must be resolved on load. |
| // - GNU_RELRO is optional but used here as we don't support lazy binding. |
| // |
| // We allow debug information to be included in the ELFs however we don't |
| // currently have a use for it at runtime. When unstripped we can possibly feed |
| // it to tools or use it ourselves to generate backtraces but since all release |
| // usage should be stripped nothing relies upon it. |
| class EmbeddedLinkerTool : public LinkerTool { |
| public: |
| using LinkerTool::LinkerTool; |
| |
| std::string getEmbeddedToolPath() const { |
| // Always try to use the tool specified for this exact configuration first. |
| // Hopefully some day soon we'll be able to statically link LLD in and call |
| // a C function to do the linking instead of needing a separate tool. |
| if (!targetOptions.embeddedLinkerPath.empty()) { |
| return targetOptions.embeddedLinkerPath; |
| } |
| |
| // Fall back to check for setting the linker explicitly via environment |
| // variables. |
| char *envVarPath = std::getenv("IREE_LLVM_EMBEDDED_LINKER_PATH"); |
| if (envVarPath && envVarPath[0] != '\0') |
| return std::string(envVarPath); |
| |
| // No explicit linker specified, search the install/build dir or env. |
| const SmallVector<std::string> &toolNames{"iree-lld", "lld", "ld.lld", |
| "lld-link"}; |
| std::string toolPath = findTool(toolNames); |
| if (!toolPath.empty()) |
| return toolPath; |
| |
| llvm::errs() |
| << "error: required embedded linker tool (typically `lld`) not found " |
| "after searching:\n" |
| " * --iree-llvmcpu-embedded-linker-path= flag\n" |
| " * IREE_LLVM_EMBEDDED_LINKER_PATH environment variable\n" |
| " * common locations at relative file paths\n" |
| " * system PATH\n" |
| "Run with --debug-only=llvm-linker for search details\n"; |
| return ""; |
| } |
| |
| LogicalResult |
| configureModule(llvm::Module *llvmModule, |
| ArrayRef<llvm::Function *> exportedFuncs) override { |
| for (auto &llvmFunc : *llvmModule) { |
| // -fno-plt - prevent PLT on calls to imports. |
| llvmFunc.addFnAttr("nonlazybind"); |
| |
| // No unwind tables are required as we don't support exceptions. |
| llvmFunc.setUWTableKind(llvm::UWTableKind::None); |
| } |
| |
| // Since all exports are fetched via the executable library query function |
| // we don't need to make them publicly exported on the module. This has |
| // the downside of making some tooling (disassemblers/decompilers/binary |
| // size analysis tools/etc) a bit harder to work with, though, so when |
| // compiling in debug mode we export all the functions. |
| if (targetOptions.target.debugSymbols) { |
| for (auto llvmFunc : exportedFuncs) { |
| llvmFunc->setVisibility( |
| llvm::GlobalValue::VisibilityTypes::DefaultVisibility); |
| llvmFunc->setLinkage(llvm::GlobalValue::LinkageTypes::ExternalLinkage); |
| |
| // In debug modes we need unwind tables in order to get proper stacks on |
| // all platforms. We don't support exceptions so unless debugging is |
| // requested we omit them to reduce code size. |
| llvmFunc->setUWTableKind(llvm::UWTableKind::Default); |
| } |
| } |
| |
| return success(); |
| } |
| |
| std::optional<Artifacts> |
| linkDynamicLibrary(StringRef libraryName, |
| ArrayRef<Artifact> objectFiles) override { |
| Artifacts artifacts; |
| |
| // Create the shared object name; if we only have a single input object we |
| // can just reuse that. |
| if (!objectFiles.empty()) { |
| artifacts.libraryFile = |
| Artifact::createVariant(objectFiles.front().path, "so"); |
| } else { |
| artifacts.libraryFile = Artifact::createTemporary(libraryName, "so"); |
| } |
| artifacts.libraryFile.close(); |
| |
| std::string embeddedToolPath = getEmbeddedToolPath(); |
| if (embeddedToolPath.empty()) |
| return std::nullopt; |
| |
| SmallVector<std::string, 8> flags = { |
| embeddedToolPath, |
| |
| // Forces LLD to act like gnu ld and produce ELF files. |
| // If not specified then lld tries to figure out what it is by progname |
| // (ld, ld64, link, etc). |
| // NOTE: must be first because lld sniffs argv[1]/argv[2]. |
| "-flavor gnu", |
| |
| "-o " + artifacts.libraryFile.path, |
| }; |
| |
| // Hide build info that makes files unreproducable. |
| flags.push_back("--build-id=none"); |
| |
| // Avoids including any libc/startup files that initialize the CRT as |
| // we don't use any of that. Our shared libraries must be freestanding. |
| flags.push_back("-nostdlib"); // -nodefaultlibs + -nostartfiles |
| |
| // Statically link all dependencies so we don't have any runtime deps. |
| // We cannot have any imports in the module we produce. |
| flags.push_back("-static"); |
| |
| // Creating a hermetic shared library. |
| flags.push_back("-shared"); |
| flags.push_back("--no-undefined"); |
| flags.push_back("--no-allow-shlib-undefined"); |
| |
| // Workaround for LLD weirdness on Windows; for some reason we get symbol |
| // conflicts only on Windows starting after |
| // https://github.com/llvm/llvm-project/commit/83d59e05b201760e3f364ff6316301d347cbad95 |
| flags.push_back("--allow-multiple-definition"); |
| |
| // Drop unused sections. |
| flags.push_back("--gc-sections"); |
| |
| // Hardening (that also makes runtime linking easier): |
| // - bind all import symbols during load |
| // - make all relocations readonly. |
| // See: https://blog.quarkslab.com/clang-hardening-cheat-sheet.html |
| flags.push_back("-z now"); |
| flags.push_back("-z relro"); |
| |
| // Strip local symbols; we only care about the global ones for lookup. |
| // This shrinks the .symtab to a single entry. |
| flags.push_back("--discard-all"); |
| |
| // Identical code folding. |
| flags.push_back("--icf=all"); |
| |
| // To aid ICF we allow functions and data to be aliased - we never expose |
| // pointers to our internal functions and don't care if they alias. |
| flags.push_back("--ignore-data-address-equality"); |
| flags.push_back("--ignore-function-address-equality"); |
| |
| // Use sysv .hash lookup table only; we have literally a single symbol and |
| // the .gnu.hash overhead is not worth it (either in the ELF or in the |
| // runtime loader). |
| flags.push_back("--hash-style=sysv"); |
| |
| // Strip debug information (only, no relocations) when not requested. |
| if (!targetOptions.target.debugSymbols) { |
| flags.push_back("--strip-debug"); |
| } |
| |
| // Link all input objects. Note that we are not linking whole-archive as |
| // we want to allow dropping of unused codegen outputs. |
| for (auto &objectFile : objectFiles) { |
| flags.push_back(objectFile.path); |
| } |
| |
| // LLD inserts its own identifier unless the LLD_VERSION env var is set: |
| // third_party/llvm-project/lld/ELF/SyntheticSections.cpp |
| if (failed(runLinkCommand(llvm::join(flags, " "), "LLD_VERSION=IREE"))) { |
| // Ensure we save inputs if we fail so that the user can replicate the |
| // command themselves. |
| if (targetOptions.keepLinkerArtifacts) { |
| for (auto &objectFile : objectFiles) { |
| if (objectFile.outputFile) { |
| llvm::errs() << "linker input preserved: " |
| << objectFile.outputFile->getFilename(); |
| objectFile.keep(); |
| } |
| } |
| } |
| return std::nullopt; |
| } |
| return artifacts; |
| } |
| }; |
| |
| std::unique_ptr<LinkerTool> |
| createEmbeddedLinkerTool(const llvm::Triple &targetTriple, |
| LLVMTargetOptions &targetOptions) { |
| return std::make_unique<EmbeddedLinkerTool>(targetTriple, targetOptions); |
| } |
| |
| } // namespace mlir::iree_compiler::IREE::HAL |