| // 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 |