| // 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 "llvm/ADT/StringExtras.h" | |
| #include "llvm/IR/Function.h" | |
| #include "llvm/IR/IRBuilder.h" | |
| #include "llvm/Support/FormatVariadic.h" | |
| #include "llvm/TargetParser/Host.h" | |
| #include "llvm/TargetParser/Triple.h" | |
| #define DEBUG_TYPE "llvm-linker" | |
| namespace mlir::iree_compiler::IREE::HAL { | |
| using llvm::Triple; | |
| // Returns the canonical host name for the Android NDK prebuilt versions: | |
| // https://developer.android.com/ndk/guides/other_build_systems | |
| // | |
| // If we want to support self-built variants we'll need an env var (or just make | |
| // the user set IREE_LLVM_SYSTEM_LINKER_PATH). | |
| static const char *getNDKHostPlatform() { | |
| auto hostTriple = Triple(llvm::sys::getProcessTriple()); | |
| if (hostTriple.isOSLinux() && hostTriple.getArch() == Triple::x86_64) { | |
| return "linux-x86_64"; | |
| } else if (hostTriple.isMacOSX() && hostTriple.getArch() == Triple::x86_64) { | |
| return "darwin-x86_64"; | |
| } else if (hostTriple.isOSWindows() && | |
| hostTriple.getArch() == Triple::x86_64) { | |
| return "windows-x86_64"; | |
| } else if (hostTriple.isOSWindows() && hostTriple.getArch() == Triple::x86) { | |
| return "windows"; | |
| } else { | |
| llvm::errs() | |
| << "No (known) Android NDK prebuilt name for this host platform ('" | |
| << hostTriple.str() << "')"; | |
| return ""; | |
| } | |
| } | |
| // Returns the canonical target name for the Android NDK prebuilt versions. | |
| static const char *getNDKTargetPlatform(const Triple &targetTriple) { | |
| switch (targetTriple.getArch()) { | |
| case Triple::arm: | |
| return "armv7a"; | |
| case Triple::aarch64: | |
| return "aarch64"; | |
| case Triple::x86: | |
| return "i686"; | |
| case Triple::x86_64: | |
| return "x86_64"; | |
| default: | |
| llvm::errs() | |
| << "No (known) Android NDK prebuilt name for this target platform ('" | |
| << targetTriple.str() << "')"; | |
| return ""; | |
| } | |
| } | |
| // Android linker using the Android NDK toolchain. | |
| // | |
| // Specifically, using the clang drivers specific to a target architecture and | |
| // API version, e.g. aarch64-linux-android29-clang. | |
| // | |
| // Do we really need to bother using the NDK and using that specific clang | |
| // driver? What if we just used a standard ld driver --- what if we just used | |
| // UnixLinkerTool for Android too? At least when the host is Linux? | |
| // At first glance, that seems to just work, but the following suggests to | |
| // still bother with Android NDK clang drivers: | |
| // | |
| // While such drivers end up exec'ing just one standard clang driver, which in | |
| // turn ends up exec'ing just one standard ld driver, at each stage some | |
| // significant flags are passed, so it seems wise to rely on the NDK to set | |
| // all these flags for us. | |
| // | |
| // Example: with strace, we find that as of NDK r23, the driver | |
| // | |
| // android-ndk-r23/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android29-clang | |
| // | |
| // execs | |
| // | |
| // android-ndk-r23/toolchains/llvm/prebuilt/linux-x86_64/bin/clang | |
| // --target=aarch64-linux-android29 | |
| // | |
| // which in turn execs | |
| // | |
| // android-ndk-r23/toolchains/llvm/prebuilt/linux-x86_64/bin/ld | |
| // -pie -z noexecstack -EL --fix-cortex-a53-843419 | |
| // --warn-shared-textrel -z now -z relro -z max-page-size=4096 | |
| // --hash-style=gnu --enable-new-dtags --eh-frame-hdr | |
| // -m aarch64linux -dynamic-linker /system/bin/linker64 | |
| // | |
| // And that ld driver really is LLD: | |
| // | |
| // $ android-ndk-r23/toolchains/llvm/prebuilt/linux-x86_64/bin/ld --version | |
| // LLD 12.0.5 (/buildbot/src/android/llvm-toolchain/out/llvm-project/lld ... | |
| class AndroidLinkerTool : public LinkerTool { | |
| public: | |
| using LinkerTool::LinkerTool; | |
| std::string getSystemToolPath() const override { | |
| auto toolPath = LinkerTool::getSystemToolPath(); | |
| if (!toolPath.empty()) { | |
| return toolPath; | |
| } | |
| // ANDROID_NDK must be set for us to infer the tool path. | |
| char *androidNDKPath = std::getenv("ANDROID_NDK"); | |
| if (!androidNDKPath) { | |
| llvm::errs() << "ANDROID_NDK environment variable must be set\n"; | |
| return ""; | |
| } | |
| // Extract the Android version from the `android30` like triple piece. | |
| llvm::VersionTuple androidEnv = targetTriple.getEnvironmentVersion(); | |
| unsigned androidVersion = androidEnv.getMajor(); // like '30' | |
| // Select prebuilt toolchain based on both host and target | |
| // architecture/platform: | |
| // https://developer.android.com/ndk/guides/other_build_systems | |
| return llvm::Twine(androidNDKPath) | |
| .concat("/toolchains/llvm/prebuilt/") | |
| .concat(getNDKHostPlatform()) | |
| .concat("/bin/") | |
| .concat(getNDKTargetPlatform(targetTriple)) | |
| .concat("-linux-android") | |
| .concat(std::to_string(androidVersion)) | |
| .concat("-clang") | |
| .str(); | |
| } | |
| 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> flags = { | |
| getSystemToolPath(), | |
| // Avoids including any libc/startup files that initialize the CRT as | |
| // we don't use any of that. Our shared libraries must be freestanding. | |
| // | |
| // It matters that this flag isn't prefixed with --for-linker=. Doing so | |
| // results in a dlopen error: 'cannot locate symbol "main" referenced by | |
| // "iree_dylib_foo.so"' | |
| "-nostdlib", // -nodefaultlibs + -nostartfiles | |
| "-o " + artifacts.libraryFile.path, | |
| }; | |
| // 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); | |
| } | |
| // Since we are using a clang driver, we need to prefix the flags that are | |
| // meant to be only interpreted by the linker. | |
| SmallVector<std::string> flagsToPrefixForLinker = { | |
| // Statically link all dependencies so we don't have any runtime deps. | |
| // We cannot have any imports in the module we produce. | |
| "-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". | |
| // | |
| // So here we are effectively relying on the linker being ld.lld, which | |
| // is the case because we are using Android NDK clang, which execs | |
| // Android NDK ld, which is ld.lld, see strace results mentioned in | |
| // AndroidLinkerTool class comment. | |
| "-shared", | |
| // As seen in the strace results mentioned in the AndroidLinkerTool | |
| // class comment, when Android NDK clang execs ld, it passes -pie to it. | |
| // ld considers that to be incompatible with -shared (maybe simply | |
| // because -pie stands for position independent EXECUTABLE and -shared | |
| // means generate an ET_DYN, not an ET_EXEC?), so we have to pass | |
| // this flag now to undo -pie. | |
| "-no-pie", | |
| }; | |
| // Strip debug information (only, no relocations) when not requested. | |
| if (!targetOptions.target.debugSymbols) { | |
| flagsToPrefixForLinker.push_back("--strip-debug"); | |
| } | |
| // Prefix and fold flagsToPrefixForLinker into flags. | |
| for (const auto &f : flagsToPrefixForLinker) { | |
| flags.push_back("--for-linker=" + f); | |
| } | |
| flagsToPrefixForLinker.clear(); | |
| auto commandLine = llvm::join(flags, " "); | |
| if (failed(runLinkCommand(commandLine))) { | |
| return std::nullopt; | |
| } | |
| return artifacts; | |
| } | |
| }; | |
| std::unique_ptr<LinkerTool> | |
| createAndroidLinkerTool(const llvm::Triple &targetTriple, | |
| LLVMTargetOptions &targetOptions) { | |
| assert(targetTriple.isAndroid() && | |
| "only use the AndroidLinkerTool for Android targets"); | |
| return std::make_unique<AndroidLinkerTool>(targetTriple, targetOptions); | |
| } | |
| } // namespace mlir::iree_compiler::IREE::HAL |