blob: 9267462250cfa265319bad385c98cc64216d9a6e [file] [log] [blame]
// 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";
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:\"{0}\\lib\\{1}\"", "%VCToolsInstallDir%", arch)
.str());
flags.push_back(llvm::formatv("/libpath:\"{0}\\Lib\\{1}\\ucrt\\{2}\"",
"%UniversalCRTSdkDir%", "%UCRTVersion%", arch)
.str());
flags.push_back(llvm::formatv("/libpath:\"{0}\\Lib\\{1}\\um\\{2}\"",
"%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