| // Copyright 2020 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 { | |
| // Unix linker (ld-like); for ELF files. | |
| class UnixLinkerTool : public LinkerTool { | |
| public: | |
| using LinkerTool::LinkerTool; | |
| std::string getSystemToolPath() const override { | |
| // First check for setting the linker explicitly. | |
| auto toolPath = LinkerTool::getSystemToolPath(); | |
| if (!toolPath.empty()) { | |
| return toolPath; | |
| } | |
| // No explicit linker specified, search the environment for common tools. | |
| // We want LLD: | |
| // * On Apple, we want the system linker, which is named `ld` | |
| if (targetIsApple()) { | |
| // On macOS, the standard system linker is `ld`, and it's | |
| // unconditionally what we want to use. | |
| toolPath = findToolInEnvironment({"ld"}); | |
| } else { | |
| // On Linux, the only linker basename that's standard is `ld` but it could | |
| // be any of ld.bfd, ld.gold, ld.lld, which are inequivalent in the way | |
| // explained in the comment below on the -shared flag. We specifically | |
| // want ld.lld here, however we still search for `ld` as a fallback name, | |
| // in case the linker would be ld.lld but would be installed only under | |
| // the name `ld`. | |
| // | |
| // Having `ld` as a fallback name also makes sense (at least | |
| // theoretically) on "generic Unix": `ld` is the standard name of the | |
| // system linker, and `-static -shared` should in theory be supported by | |
| // the system linker (as suggested by both the FreeBSD and GNU man pages | |
| // for ld). | |
| // | |
| // On the other hand, on Linux where the possible fallbacks are ld.bfd or | |
| // ld.gold, we are specifically not interested in falling back on any | |
| // of these, at least given current behavior. | |
| toolPath = findToolInEnvironment({"ld.lld", "ld"}); | |
| } | |
| if (!toolPath.empty()) { | |
| return toolPath; | |
| } | |
| llvm::errs() << "No Unix linker tool found in environment.\n"; | |
| return ""; | |
| } | |
| 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.size() == 1) { | |
| artifacts.libraryFile = | |
| Artifact::createVariant(objectFiles.front().path, "so"); | |
| } else { | |
| artifacts.libraryFile = Artifact::createTemporary(libraryName, "so"); | |
| } | |
| artifacts.libraryFile.close(); | |
| SmallVector<std::string, 8> flags = { | |
| getSystemToolPath(), | |
| "-o " + artifacts.libraryFile.path, | |
| }; | |
| if (targetIsApple()) { | |
| // 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"); | |
| // Produce a Mach-O dylib file. | |
| flags.push_back("-dylib"); | |
| flags.push_back("-flat_namespace"); | |
| flags.push_back( | |
| "-L /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib " | |
| "-lSystem"); | |
| } else { | |
| // 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"); | |
| // Generate a dynamic library (ELF type: ET_DYN), otherwise dlopen() | |
| // won't succeed on it. This is not incompatible with -static. The GNU | |
| // man page for ld, `man ld`, says the following: | |
| // | |
| // -static | |
| // Do not link against shared libraries. [...] This option can be | |
| // used with -shared. Doing so means that a shared library is | |
| // being created but that all of the library's external references | |
| // must be resolved by pulling in entries from static libraries. | |
| // | |
| // While that much is said in the GNU ld man page, the reality is that | |
| // out of ld.bfd, ld.gold and ld.lld, only ld.lld actually implements | |
| // that. Meanwhile, ld.bfd interprets -static -shared as just -static, | |
| // and ld.gold rejects -static -shared outright as "incompatible". | |
| flags.push_back("-shared"); | |
| } | |
| // 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); | |
| } | |
| auto commandLine = llvm::join(flags, " "); | |
| if (failed(runLinkCommand(commandLine))) { | |
| return std::nullopt; | |
| } | |
| return artifacts; | |
| } | |
| private: | |
| bool targetIsApple() const { | |
| return targetTriple.isOSDarwin() || targetTriple.isiOS(); | |
| } | |
| }; | |
| std::unique_ptr<LinkerTool> | |
| createUnixLinkerTool(const llvm::Triple &targetTriple, | |
| LLVMTargetOptions &targetOptions) { | |
| return std::make_unique<UnixLinkerTool>(targetTriple, targetOptions); | |
| } | |
| } // namespace mlir::iree_compiler::IREE::HAL |