|  | // 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/FormatVariadic.h" | 
|  |  | 
|  | #define DEBUG_TYPE "llvm-linker" | 
|  |  | 
|  | namespace mlir::iree_compiler::IREE::HAL { | 
|  |  | 
|  | // Windows linker (MSVC link.exe-like); for DLL files. | 
|  | class WindowsLinkerTool : 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 executable directory (i.e. our | 
|  | // own build or install directories) for common tools. | 
|  | toolPath = findToolFromExecutableDir({"lld-link"}); | 
|  | if (!toolPath.empty()) | 
|  | return toolPath; | 
|  |  | 
|  | llvm::errs() << "No Windows linker tool specified or discovered\n"; | 
|  | return ""; | 
|  | } | 
|  |  | 
|  | LogicalResult | 
|  | configureModule(llvm::Module *llvmModule, | 
|  | ArrayRef<llvm::Function *> exportedFuncs) override { | 
|  | auto &ctx = llvmModule->getContext(); | 
|  |  | 
|  | // Create a _DllMainCRTStartup replacement that does not initialize the CRT. | 
|  | // This is required to prevent a bunch of CRT junk (locale, errno, TLS, etc) | 
|  | // from getting emitted in such a way that it cannot be stripped by LTCG. | 
|  | // Since we don't emit code using the CRT (beyond memset/memcpy) this is | 
|  | // fine and can reduce binary sizes by 50-100KB. | 
|  | // | 
|  | // More info: | 
|  | // https://docs.microsoft.com/en-us/cpp/build/run-time-library-behavior?view=vs-2019 | 
|  | { | 
|  | auto dwordType = llvm::IntegerType::get(ctx, 32); | 
|  | auto ptrType = llvm::PointerType::getUnqual(dwordType); | 
|  | auto entry = cast<llvm::Function>( | 
|  | llvmModule | 
|  | ->getOrInsertFunction("iree_dll_main", dwordType, ptrType, | 
|  | dwordType, ptrType) | 
|  | .getCallee()); | 
|  | entry->setCallingConv(llvm::CallingConv::X86_StdCall); | 
|  | entry->setDLLStorageClass( | 
|  | llvm::GlobalValue::DLLStorageClassTypes::DLLExportStorageClass); | 
|  | entry->setLinkage(llvm::GlobalValue::LinkageTypes::ExternalLinkage); | 
|  | auto *block = llvm::BasicBlock::Create(ctx, "entry", entry); | 
|  | llvm::IRBuilder<> builder(block); | 
|  | auto one = llvm::ConstantInt::get(dwordType, 1, false); | 
|  | builder.CreateRet(one); | 
|  | } | 
|  |  | 
|  | // 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); | 
|  | llvmFunc->setDLLStorageClass( | 
|  | llvm::GlobalValue::DLLStorageClassTypes::DLLExportStorageClass); | 
|  |  | 
|  | // 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::Async); | 
|  | } | 
|  | } | 
|  |  | 
|  | 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.size() == 1) { | 
|  | artifacts.libraryFile = | 
|  | Artifact::createVariant(objectFiles.front().path, "dll"); | 
|  | } else { | 
|  | artifacts.libraryFile = Artifact::createTemporary(libraryName, "dll"); | 
|  | } | 
|  |  | 
|  | // link.exe doesn't like the files being opened. We don't use them as | 
|  | // streams so close them all now before running the linker. | 
|  | artifacts.libraryFile.close(); | 
|  |  | 
|  | // We need a full path for the PDB and I hate strings in LLVM grumble. | 
|  | SmallString<32> pdbPath(artifacts.libraryFile.path); | 
|  | llvm::sys::path::replace_extension(pdbPath, "pdb"); | 
|  |  | 
|  | SmallVector<std::string, 8> flags = { | 
|  | getSystemToolPath(), | 
|  |  | 
|  | // Hide the linker banner message printed each time. | 
|  | "/nologo", | 
|  |  | 
|  | // Useful when debugging linking/loading issues: | 
|  | // "/verbose", | 
|  |  | 
|  | // https://docs.microsoft.com/en-us/cpp/build/reference/dll-build-a-dll?view=vs-2019 | 
|  | // Builds a DLL and exports functions with the dllexport storage class. | 
|  | "/dll", | 
|  |  | 
|  | // Forces a fixed timestamp to ensure files are reproducible across | 
|  | // builds. Undocumented but accepted by both link and lld-link. | 
|  | // https://blog.conan.io/2019/09/02/Deterministic-builds-with-C-C++.html | 
|  | "/Brepro", | 
|  |  | 
|  | // https://docs.microsoft.com/en-us/cpp/build/reference/nodefaultlib-ignore-libraries?view=vs-2019 | 
|  | // Ignore any libraries that are specified by the platform as we | 
|  | // directly provide the ones we want. | 
|  | "/nodefaultlib", | 
|  |  | 
|  | // https://docs.microsoft.com/en-us/cpp/build/reference/incremental-link-incrementally?view=vs-2019 | 
|  | // Disable incremental linking as we are only ever linking in one-shot | 
|  | // mode to temp files. This avoids additional file padding and ordering | 
|  | // restrictions that enable incremental linking. Our other options will | 
|  | // prevent incremental linking in most cases, but it doesn't hurt to be | 
|  | // explicit. | 
|  | "/incremental:no", | 
|  |  | 
|  | // https://docs.microsoft.com/en-us/cpp/build/reference/guard-enable-guard-checks?view=vs-2019 | 
|  | // No control flow guard lookup (indirect branch verification). | 
|  | "/guard:no", | 
|  |  | 
|  | // https://docs.microsoft.com/en-us/cpp/build/reference/safeseh-image-has-safe-exception-handlers?view=vs-2019 | 
|  | // We don't want exception unwind tables in our output. | 
|  | "/safeseh:no", | 
|  |  | 
|  | // https://docs.microsoft.com/en-us/cpp/build/reference/entry-entry-point-symbol?view=vs-2019 | 
|  | // Use our entry point instead of the standard CRT one; ensures that we | 
|  | // pull in no global state from the CRT. | 
|  | "/entry:iree_dll_main", | 
|  |  | 
|  | // https://docs.microsoft.com/en-us/cpp/build/reference/debug-generate-debug-info?view=vs-2019 | 
|  | // Copies all PDB information into the final PDB so that we can use the | 
|  | // same PDB across multiple machines. | 
|  | "/debug:full", | 
|  |  | 
|  | // https://docs.microsoft.com/en-us/cpp/build/reference/pdb-use-program-database | 
|  | // Generates the PDB file containing the debug information. | 
|  | ("/pdb:" + pdbPath).str(), | 
|  |  | 
|  | // https://docs.microsoft.com/en-us/cpp/build/reference/pdbaltpath-use-alternate-pdb-path?view=vs-2019 | 
|  | // Forces the PDB we generate to be referenced in the DLL as just a | 
|  | // relative path to the DLL itself. This allows us to move the PDBs | 
|  | // along with the build DLLs across machines. | 
|  | "/pdbaltpath:%_PDB%", | 
|  |  | 
|  | // https://docs.microsoft.com/en-us/cpp/build/reference/out-output-file-name?view=vs-2019 | 
|  | // Target for linker output. The base name of this path will be used for | 
|  | // additional output files (like the map and pdb). | 
|  | "/out:" + artifacts.libraryFile.path, | 
|  | }; | 
|  |  | 
|  | if (targetOptions.target.optimizerOptLevel.getSpeedupLevel() >= 2 || | 
|  | targetOptions.target.optimizerOptLevel.getSizeLevel() >= 2) { | 
|  | // https://docs.microsoft.com/en-us/cpp/build/reference/opt-optimizations?view=vs-2019 | 
|  | // Enable all the fancy optimizations. | 
|  | flags.push_back("/opt:ref,icf,lbr"); | 
|  | } | 
|  |  | 
|  | // SDK and MSVC paths. | 
|  | // These rely on the environment variables provided by the | 
|  | // vcvarsall or VsDevCmd ("Developer Command Prompt") scripts. They can also | 
|  | // be manually be specified. | 
|  | // | 
|  | // We could also check to see if vswhere is installed and query that in the | 
|  | // event of missing environment variables; that would eliminate the need for | 
|  | // specifying things from for example IDEs that may not bring in the vcvars. | 
|  | // | 
|  | /* Example values: | 
|  | UCRTVersion=10.0.18362.0 | 
|  | UniversalCRTSdkDir=C:\Program Files (x86)\Windows Kits\10\ | 
|  | VCToolsInstallDir=C:\Program Files (x86)\Microsoft Visual | 
|  | Studio\2019\Preview\VC\Tools\MSVC\14.28.29304\ | 
|  | */ | 
|  | if (!getenv("VCToolsInstallDir") || !getenv("UniversalCRTSdkDir")) { | 
|  | llvm::errs() << "required environment for lld-link/link not specified; " | 
|  | "ensure you are building from a shell where " | 
|  | "vcvarsall/VsDevCmd.bat/etc has been used\n"; | 
|  | return std::nullopt; | 
|  | } | 
|  | const char *arch; | 
|  | if (targetTriple.isARM() && targetTriple.isArch32Bit()) { | 
|  | arch = "arm"; | 
|  | } else if (targetTriple.isARM()) { | 
|  | arch = "arm64"; | 
|  | } else if (targetTriple.isX86() && targetTriple.isArch32Bit()) { | 
|  | arch = "x86"; | 
|  | } else if (targetTriple.isX86()) { | 
|  | arch = "x64"; | 
|  | } else { | 
|  | llvm::errs() << "unsupported Windows target triple (no arch libs): " | 
|  | << targetTriple.str(); | 
|  | return std::nullopt; | 
|  | } | 
|  | flags.push_back( | 
|  | llvm::formatv("/libpath:\"{}\\lib\\{}\"", "%VCToolsInstallDir%", arch) | 
|  | .str()); | 
|  | flags.push_back(llvm::formatv("/libpath:\"{}\\Lib\\{}\\ucrt\\{}\"", | 
|  | "%UniversalCRTSdkDir%", "%UCRTVersion%", arch) | 
|  | .str()); | 
|  | flags.push_back(llvm::formatv("/libpath:\"{}\\Lib\\{}\\um\\{}\"", | 
|  | "%UniversalCRTSdkDir%", "%UCRTVersion%", arch) | 
|  | .str()); | 
|  |  | 
|  | // We need to link against different libraries based on our configuration | 
|  | // matrix (dynamic/static and debug/release). | 
|  | int libIndex = 0; | 
|  | if (targetOptions.target.optimizerOptLevel.getSpeedupLevel() == 0) { | 
|  | libIndex += 0; // debug | 
|  | } else { | 
|  | libIndex += 2; // release | 
|  | } | 
|  | libIndex += targetOptions.target.linkStatic ? 1 : 0; | 
|  |  | 
|  | // The required libraries for linking DLLs: | 
|  | // https://docs.microsoft.com/en-us/cpp/c-runtime-library/crt-library-features?view=msvc-160 | 
|  | // | 
|  | // NOTE: there are only static versions of msvcrt as it's the startup code. | 
|  | static const char *kMSVCRTLibs[4] = { | 
|  | /*   debug/dynamic */ "msvcrtd.lib", | 
|  | /*   debug/static  */ "msvcrtd.lib", | 
|  | /* release/dynamic */ "msvcrt.lib", | 
|  | /* release/static  */ "msvcrt.lib", | 
|  | }; | 
|  | static const char *kVCRuntimeLibs[4] = { | 
|  | /*   debug/dynamic */ "vcruntimed.lib", | 
|  | /*   debug/static  */ "libvcruntimed.lib", | 
|  | /* release/dynamic */ "vcruntime.lib", | 
|  | /* release/static  */ "libvcruntime.lib", | 
|  | }; | 
|  | static const char *kUCRTLibs[4] = { | 
|  | /*   debug/dynamic */ "ucrtd.lib", | 
|  | /*   debug/static  */ "libucrtd.lib", | 
|  | /* release/dynamic */ "ucrt.lib", | 
|  | /* release/static  */ "libucrt.lib", | 
|  | }; | 
|  | flags.push_back(kMSVCRTLibs[libIndex]); | 
|  | flags.push_back(kVCRuntimeLibs[libIndex]); | 
|  | flags.push_back(kUCRTLibs[libIndex]); | 
|  | flags.push_back("kernel32.lib"); | 
|  |  | 
|  | // 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; | 
|  |  | 
|  | // PDB file gets generated wtih the same path + .pdb. | 
|  | artifacts.debugFile = | 
|  | Artifact::createVariant(artifacts.libraryFile.path, "pdb"); | 
|  |  | 
|  | // We currently discard some of the other file outputs (like the .exp | 
|  | // listing the exported symbols) as we don't need them. | 
|  | artifacts.otherFiles.push_back( | 
|  | Artifact::createVariant(artifacts.libraryFile.path, "exp")); | 
|  | artifacts.otherFiles.push_back( | 
|  | Artifact::createVariant(artifacts.libraryFile.path, "lib")); | 
|  |  | 
|  | return artifacts; | 
|  | } | 
|  | }; | 
|  |  | 
|  | std::unique_ptr<LinkerTool> | 
|  | createWindowsLinkerTool(const llvm::Triple &targetTriple, | 
|  | LLVMTargetOptions &targetOptions) { | 
|  | return std::make_unique<WindowsLinkerTool>(targetTriple, targetOptions); | 
|  | } | 
|  |  | 
|  | } // namespace mlir::iree_compiler::IREE::HAL |