blob: 2818949aec32d4dba7ea86c5364da031f01bb801 [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 "iree/compiler/Dialect/HAL/Target/LLVM/LLVMAOTTarget.h"
#include <cstdlib>
#include "iree-dialects/Dialect/LinalgExt/IR/LinalgExtDialect.h"
#include "iree-dialects/Dialect/LinalgTransform/LinalgTransformOps.h"
#include "iree/compiler/Codegen/Dialect/IREECodegenDialect.h"
#include "iree/compiler/Codegen/Passes.h"
#include "iree/compiler/Dialect/HAL/Target/LLVM/Builtins/Device.h"
#include "iree/compiler/Dialect/HAL/Target/LLVM/Builtins/Musl.h"
#include "iree/compiler/Dialect/HAL/Target/LLVM/LLVMIRPasses.h"
#include "iree/compiler/Dialect/HAL/Target/LLVM/LibraryBuilder.h"
#include "iree/compiler/Dialect/HAL/Target/LLVM/LinkerTool.h"
#include "iree/compiler/Dialect/HAL/Target/LLVM/StaticLibraryGenerator.h"
#include "iree/compiler/Dialect/HAL/Target/TargetRegistry.h"
#include "llvm/Analysis/TargetTransformInfo.h"
#include "llvm/Bitcode/BitcodeReader.h"
#include "llvm/Bitcode/BitcodeWriter.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/Linker/Linker.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/TargetSelect.h"
#include "mlir/Dialect/ArmNeon/ArmNeonDialect.h"
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
#include "mlir/Dialect/PDL/IR/PDL.h"
#include "mlir/Dialect/PDLInterp/IR/PDLInterp.h"
#include "mlir/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.h"
#include "mlir/Target/LLVMIR/Export.h"
#define DEBUG_TYPE "iree-llvmaot-target"
namespace mlir {
namespace iree_compiler {
namespace IREE {
namespace HAL {
static constexpr char kQueryFunctionName[] =
"iree_hal_executable_library_query";
static llvm::Optional<FileLineColLoc> findFirstFileLoc(Location baseLoc) {
if (auto loc = baseLoc.dyn_cast<FusedLoc>()) {
for (auto &childLoc : loc.getLocations()) {
auto childResult = findFirstFileLoc(childLoc);
if (childResult) return childResult;
}
} else if (auto loc = baseLoc.dyn_cast<FileLineColLoc>()) {
return loc;
}
return llvm::None;
}
static std::string guessModuleName(mlir::ModuleOp moduleOp) {
std::string moduleName =
moduleOp.getName().hasValue() ? moduleOp.getName().getValue().str() : "";
if (!moduleName.empty()) return moduleName;
auto loc = findFirstFileLoc(moduleOp.getLoc());
if (loc.hasValue()) {
return llvm::sys::path::stem(loc.getValue().getFilename()).str();
} else {
return "llvm_module";
}
}
// Appends the |debugDatabase| to the end of |baseFile| and writes the footer
// so the runtime can find it.
static LogicalResult appendDebugDatabase(std::vector<int8_t> &baseFile,
Artifact &debugFileArtifact) {
auto debugFileOr = debugFileArtifact.read();
if (!debugFileOr.hasValue()) {
return failure();
}
auto debugFile = std::move(debugFileOr).getValue();
// NOTE: we align the sizes so that the files all start at nice offsets.
auto baseFileSize = IREE::Util::align(baseFile.size(), 16);
auto debugFileSize = IREE::Util::align(debugFile.size(), 16);
// Matches iree_hal_system_executable_footer_t.
struct Footer {
uint8_t magic[8]; // IREEDBG\0
uint32_t version;
uint32_t flags;
uint64_t libraryOffset;
uint64_t librarySize;
uint64_t debugOffset;
uint64_t debugSize;
} footer = {{0}};
std::memcpy(footer.magic, "IREEDBG\0", sizeof(footer.magic));
footer.version = 0;
footer.librarySize = baseFile.size();
footer.debugOffset = baseFileSize;
footer.debugSize = debugFile.size();
baseFile.resize(baseFileSize + debugFileSize + sizeof(footer));
std::memcpy(baseFile.data() + baseFileSize, debugFile.data(),
debugFile.size());
std::memcpy(baseFile.data() + baseFileSize + debugFileSize, &footer,
sizeof(footer));
return success();
}
// Verifies builtin bitcode is loaded correctly and appends it to |linker|.
//
// Example:
// if (failed(linkBuiltinLibrary(loc, linker, linkerFlag, targetMachine,
// "libfoo", loadLibFoo(...))))
static LogicalResult linkBuiltinLibrary(
Location loc, llvm::Linker &linker, llvm::Linker::Flags linkerFlag,
llvm::TargetMachine *targetMachine, StringRef name,
llvm::Expected<std::unique_ptr<llvm::Module>> bitcodeModuleValue) {
// Ensure the bitcode loaded correctly. It may fail if the LLVM version is
// incompatible.
if (!bitcodeModuleValue) {
return mlir::emitError(loc)
<< "failed to parse " << name
<< " bitcode: " << llvm::toString(bitcodeModuleValue.takeError())
<< " (possible LLVM bitcode incompatibility?)";
}
auto bitcodeModule = std::move(bitcodeModuleValue.get());
bitcodeModule->setDataLayout(targetMachine->createDataLayout());
bitcodeModule->setTargetTriple(targetMachine->getTargetTriple().str());
// Link the bitcode into the base module. This will merge in any required
// symbols and override declarations that may exist.
if (linker.linkInModule(std::move(bitcodeModule), linkerFlag)) {
return mlir::emitError(loc) << "failed to link " << name << " bitcode";
}
return success();
}
class LLVMAOTTargetBackend final : public TargetBackend {
public:
explicit LLVMAOTTargetBackend(LLVMTargetOptions options)
: options_(std::move(options)) {
initConfiguration();
}
std::string name() const override { return "llvm"; }
std::string deviceID() const override { return "cpu"; }
void getDependentDialects(DialectRegistry &registry) const override {
mlir::registerLLVMDialectTranslation(registry);
// TODO: make inclusion of ArmNeon conditional?
// clang-format off
registry.insert<IREE::Codegen::IREECodegenDialect,
IREE::LinalgExt::IREELinalgExtDialect,
linalg::transform::LinalgTransformDialect,
pdl::PDLDialect,
pdl_interp::PDLInterpDialect,
arm_neon::ArmNeonDialect>();
// clang-format on
}
IREE::HAL::DeviceTargetAttr getDefaultDeviceTarget(
MLIRContext *context) const override {
Builder b(context);
SmallVector<NamedAttribute> configItems;
configItems.emplace_back(b.getStringAttr("executable_targets"),
getExecutableTargets(context));
auto configAttr = b.getDictionaryAttr(configItems);
return IREE::HAL::DeviceTargetAttr::get(
context, b.getStringAttr(deviceID()), configAttr);
}
void buildTranslationPassPipeline(OpPassManager &passManager) override {
buildLLVMCPUCodegenPassPipeline(passManager);
}
LogicalResult linkExecutables(mlir::ModuleOp moduleOp) override {
OpBuilder builder = OpBuilder::atBlockBegin(moduleOp.getBody());
auto sourceExecutableOps =
llvm::to_vector<8>(moduleOp.getOps<IREE::HAL::ExecutableOp>());
if (sourceExecutableOps.size() <= 1) return success();
// TODO(benvanik): rework linking to support multiple formats.
auto sharedTargetAttr = getExecutableTarget(builder.getContext());
// Guess a module name, if needed, to make the output files readable.
auto moduleName = guessModuleName(moduleOp);
// Create our new "linked" hal.executable.
std::string linkedExecutableName =
llvm::formatv("{0}_linked_{1}", moduleName, name());
auto linkedExecutableOp = builder.create<IREE::HAL::ExecutableOp>(
moduleOp.getLoc(), linkedExecutableName);
linkedExecutableOp.setVisibility(
sourceExecutableOps.front().getVisibility());
// Add our hal.executable.variant with an empty module.
builder.setInsertionPointToStart(linkedExecutableOp.getBody());
auto linkedTargetOp = builder.create<IREE::HAL::ExecutableVariantOp>(
moduleOp.getLoc(), sharedTargetAttr.getSymbolNameFragment(),
sharedTargetAttr);
builder.setInsertionPoint(&linkedTargetOp.getBlock().back());
builder.create<ModuleOp>(moduleOp.getLoc());
// Try linking together all executables in moduleOp.
return linkExecutablesInto(
moduleOp, sourceExecutableOps, linkedExecutableOp, linkedTargetOp,
[](mlir::ModuleOp moduleOp) { return moduleOp; }, builder);
}
LogicalResult serializeExecutable(IREE::HAL::ExecutableVariantOp variantOp,
OpBuilder &executableBuilder) override {
// Perform the translation in a separate context to avoid any
// multi-threading issues.
llvm::LLVMContext context;
// We name our files after the executable name so that they are easy to
// track both during compilation (logs/artifacts/etc), as outputs (final
// intermediate code/binary files), and at runtime (loaded
// libraries/symbols/etc).
auto libraryName =
variantOp->getParentOfType<IREE::HAL::ExecutableOp>().getName().str();
// Validate flags for output mode.
if (options_.linkEmbedded && options_.linkStatic) {
return variantOp.emitError()
<< "cannot embed ELF and produce static library simultaneously";
}
// Specialize the module to the target triple.
// The executable will have been cloned into other ExecutableVariantOps for
// other triples so it's fine to mutate in-place.
llvm::Triple targetTriple(options_.targetTriple);
variantOp.getInnerModule()->setAttr(
LLVM::LLVMDialect::getTargetTripleAttrName(),
executableBuilder.getStringAttr(targetTriple.str()));
// At this moment we are leaving MLIR LLVM dialect land translating module
// into target independent LLVMIR.
auto llvmModule = mlir::translateModuleToLLVMIR(variantOp.getInnerModule(),
context, libraryName);
if (!llvmModule) {
return variantOp.emitError() << "failed to translate the MLIR LLVM "
"dialect to the native llvm::Module";
}
// Configure the functions in the module. This may override defaults set
// during the MLIR->LLVM conversion.
for (auto &func : *llvmModule) {
// Enable frame pointers to ensure that stack unwinding works, e.g. in
// Tracy. In principle this could also be achieved by enabling unwind
// tables, but we tried that and that didn't work in Tracy (which uses
// libbacktrace), while enabling frame pointers worked.
// https://github.com/google/iree/issues/3957
func.addFnAttr("frame-pointer", "all");
// -ffreestanding-like behavior.
func.addFnAttr("no-builtins");
// Our dispatches are all hot - that's kind of the point.
// This may favor more aggressive optimizations.
func.addFnAttr("hot");
}
// Build the IREE HAL executable library metadata. The runtime uses this to
// find the entry point functions and their information.
LibraryBuilder::Mode libraryBuilderMode =
options_.debugSymbols ? LibraryBuilder::Mode::INCLUDE_REFLECTION_ATTRS
: LibraryBuilder::Mode::NONE;
LibraryBuilder libraryBuilder(llvmModule.get(), libraryBuilderMode,
LibraryBuilder::Version::LATEST);
switch (options_.sanitizerKind) {
case SanitizerKind::kNone: {
libraryBuilder.setSanitizerKind(LibraryBuilder::SanitizerKind::NONE);
break;
}
case SanitizerKind::kAddress: {
libraryBuilder.setSanitizerKind(LibraryBuilder::SanitizerKind::ADDRESS);
for (auto &function : llvmModule->getFunctionList()) {
function.addFnAttr(llvm::Attribute::SanitizeAddress);
}
} break;
}
auto align16 = llvm::Attribute::getWithAlignment(context, llvm::Align(16));
for (auto entryPointOp :
variantOp.getBlock().getOps<ExecutableEntryPointOp>()) {
// Find the matching function in the LLVM module.
auto *llvmFunc = llvmModule->getFunction(entryPointOp.getName());
llvmFunc->setLinkage(llvm::GlobalValue::LinkageTypes::InternalLinkage);
llvmFunc->setDSOLocal(true);
// Tag the function parameters in case they got removed during conversion.
// (%arg0: environment, %arg1: dispatch_state, %arg2: workgroup_state)
for (unsigned i = 0; i <= 2; ++i) {
llvmFunc->addParamAttr(
i, llvm::Attribute::getWithByRefType(
context, llvmFunc->getArg(i)
->getType()
->getNonOpaquePointerElementType()));
llvmFunc->addParamAttr(i, llvm::Attribute::NonNull);
llvmFunc->addParamAttr(i, llvm::Attribute::NoAlias);
llvmFunc->addParamAttr(i, align16);
}
// Optionally entry points may specify that they require workgroup local
// memory. We fetch that value here and plumb it through so the runtime
// knows how much memory to reserve and pass in.
int64_t localMemorySize = entryPointOp.workgroup_local_memory()
.getValueOr(APInt(64, 0))
.getSExtValue();
libraryBuilder.addExport(entryPointOp.getName(), "",
LibraryBuilder::DispatchAttrs{localMemorySize},
llvmFunc);
}
auto queryFunctionName = std::string(kQueryFunctionName);
if (options_.linkStatic) {
// Static library query functions must be unique to support multiple
// libraries in the same namespace.
queryFunctionName = libraryName + "_library_query";
}
auto *queryLibraryFunc = libraryBuilder.build(queryFunctionName);
// The query function must be exported for dynamic libraries.
queryLibraryFunc->setVisibility(
llvm::GlobalValue::VisibilityTypes::DefaultVisibility);
queryLibraryFunc->setLinkage(
llvm::GlobalValue::LinkageTypes::ExternalLinkage);
queryLibraryFunc->setDSOLocal(false);
// If linking dynamically, find a suitable linker tool and configure the
// module with any options that tool requires.
std::unique_ptr<LinkerTool> linkerTool;
if (!options_.linkStatic) {
// Grab a linker tool based on the options (and target environment).
linkerTool = LinkerTool::getForTarget(targetTriple, options_);
if (!linkerTool) {
return mlir::emitError(variantOp.getLoc())
<< "failed to find a target linker for the given target triple '"
<< options_.targetTriple << "'";
}
// Configure the module with any code generation options required later by
// linking (such as initializer functions).
if (failed(linkerTool->configureModule(llvmModule.get(),
{queryLibraryFunc}))) {
return variantOp.emitError()
<< "failed to configure LLVM module for target linker";
}
}
// Specialize the module to our target machine.
auto targetMachine = createTargetMachine(options_);
if (!targetMachine) {
return mlir::emitError(variantOp.getLoc())
<< "failed to create target machine for target triple '"
<< options_.targetTriple << "'";
}
llvmModule->setDataLayout(targetMachine->createDataLayout());
llvmModule->setTargetTriple(targetMachine->getTargetTriple().str());
// Statically link libraries into our module.
// Note that if producing a static library then the symbols we add must be
// weak such that we don't trigger ODR issues.
llvm::Linker moduleLinker(*llvmModule);
llvm::Linker::Flags linkerFlag = llvm::Linker::OverrideFromSrc;
if (options_.linkStatic) linkerFlag = llvm::Linker::LinkOnlyNeeded;
if (failed(linkBuiltinLibrary(
variantOp.getLoc(), moduleLinker, linkerFlag, targetMachine.get(),
"libdevice", loadDeviceBitcode(targetMachine.get(), context)))) {
return mlir::emitError(variantOp.getLoc())
<< "failed linking in builtin library for target triple '"
<< options_.targetTriple << "'";
}
if (failed(linkBuiltinLibrary(
variantOp.getLoc(), moduleLinker, linkerFlag, targetMachine.get(),
"libmusl", loadMuslBitcode(targetMachine.get(), context)))) {
return mlir::emitError(variantOp.getLoc())
<< "failed linking in builtin library for target triple '"
<< options_.targetTriple << "'";
}
// Strip any compiler identifiers that may have snuck in. We let the linker
// tag the module.
auto *llvmIdent = llvmModule->getNamedMetadata("llvm.ident");
if (llvmIdent) llvmIdent->clearOperands();
// LLVM opt passes that perform code generation optimizations/transformation
// similar to what a frontend would do.
if (failed(
runLLVMIRPasses(options_, targetMachine.get(), llvmModule.get()))) {
return variantOp.emitError()
<< "failed to run LLVM-IR opt passes for IREE::HAL::ExecutableOp "
"targeting '"
<< options_.targetTriple << "'";
}
// Fixup visibility from any symbols we may link in - we want to hide all
// but the query entry point.
for (auto &func : *llvmModule) {
if (&func == queryLibraryFunc) {
// Leave our library query function as public/external so that it is
// exported from shared objects and available for linking in static
// objects.
continue;
} else if (func.isDeclaration()) {
// Declarations must have their original visibility/linkage; they most
// often come from declared llvm builtin ops (llvm.memcpy/etc).
continue;
}
func.setDSOLocal(true);
func.setLinkage(llvm::GlobalValue::LinkageTypes::InternalLinkage);
}
for (auto &global : llvmModule->getGlobalList()) {
global.setDSOLocal(true);
global.setLinkage(llvm::GlobalValue::LinkageTypes::InternalLinkage);
}
SmallVector<Artifact> objectFiles;
// Emit the base object file containing the bulk of our code.
// This must come first such that we have the proper library linking order.
{
// NOTE: today we just use a single object file, however if we wanted to
// scale code generation and linking we'd want to generate one per
// function (or something like that). A single object file is also
// instrumental to static library generation (which only supports one
// object file per library).
std::string objectData;
if (failed(runEmitObjFilePasses(targetMachine.get(), llvmModule.get(),
llvm::CGFT_ObjectFile, &objectData))) {
return variantOp.emitError()
<< "failed to compile LLVM-IR module to an object file";
}
auto objectFile = Artifact::createTemporary(libraryName, "o");
auto &os = objectFile.outputFile->os();
os << objectData;
os.flush();
os.close();
objectFiles.push_back(std::move(objectFile));
}
// If we are keeping artifacts then let's also add the bitcode and
// assembly listing for easier debugging (vs just the binary object file).
if (options_.keepLinkerArtifacts) {
std::string asmData;
if (failed(runEmitObjFilePasses(targetMachine.get(), llvmModule.get(),
llvm::CGFT_AssemblyFile, &asmData))) {
return variantOp.emitError()
<< "failed to compile LLVM-IR module to an assembly file";
}
{
auto asmFile = Artifact::createVariant(objectFiles.front().path, "s");
auto &os = asmFile.outputFile->os();
os << asmData;
os.flush();
os.close();
asmFile.outputFile->keep();
}
{
auto bitcodeFile =
Artifact::createVariant(objectFiles.front().path, "bc");
auto &os = bitcodeFile.outputFile->os();
llvm::WriteBitcodeToFile(*llvmModule, os);
os.flush();
os.close();
bitcodeFile.outputFile->keep();
}
}
if (options_.linkStatic) {
return serializeStaticLibraryExecutable(variantOp, executableBuilder,
libraryName, queryFunctionName,
objectFiles);
} else {
return serializeDynamicLibraryExecutable(variantOp, executableBuilder,
libraryName, objectFiles,
linkerTool.get());
}
}
LogicalResult serializeStaticLibraryExecutable(
IREE::HAL::ExecutableVariantOp variantOp, OpBuilder &executableBuilder,
const std::string &libraryName, const std::string &queryFunctionName,
const SmallVector<Artifact> &objectFiles) {
if (objectFiles.size() != 1) {
// Static library output only supports single object libraries.
return variantOp.emitError() << "generating static libraries from "
"multiple object files is not supported";
}
// Copy the static object file to the specified output along with
// generated header file.
if (!outputStaticLibrary(libraryName, queryFunctionName,
options_.staticLibraryOutput,
objectFiles[0].path)) {
return variantOp.emitError() << "static library generation failed";
}
// Embed the library name in the executable binary op. This informs the
// loader which static library to load for the target binary.
std::vector<uint8_t> libraryNameVector(libraryName.begin(),
libraryName.end());
executableBuilder.create<IREE::HAL::ExecutableBinaryOp>(
variantOp.getLoc(), variantOp.sym_name(), "static", libraryNameVector);
return success();
}
LogicalResult serializeDynamicLibraryExecutable(
IREE::HAL::ExecutableVariantOp variantOp, OpBuilder &executableBuilder,
const std::string &libraryName, const SmallVector<Artifact> &objectFiles,
LinkerTool *linkerTool) {
// Link the generated object files into a dylib.
auto linkArtifactsOr =
linkerTool->linkDynamicLibrary(libraryName, objectFiles);
if (!linkArtifactsOr.hasValue()) {
return mlir::emitError(variantOp.getLoc())
<< "failed to link executable and generate target dylib (check "
"above for more specific error messages)";
}
auto &linkArtifacts = linkArtifactsOr.getValue();
if (options_.keepLinkerArtifacts) {
mlir::emitRemark(variantOp.getLoc())
<< "linker artifacts for " << variantOp.getName() << " preserved:\n"
<< " " << linkArtifacts.libraryFile.path;
linkArtifacts.keepAllFiles();
for (auto &objectFile : objectFiles) {
objectFile.outputFile->keep();
}
}
if (options_.linkEmbedded) {
// Load the linked ELF file and pack into an attr.
auto elfFile = linkArtifacts.libraryFile.read();
if (!elfFile.hasValue()) {
return variantOp.emitError()
<< "failed to read back dylib temp file at "
<< linkArtifacts.libraryFile.path;
}
auto bufferAttr = DenseIntElementsAttr::get(
VectorType::get({static_cast<int64_t>(elfFile->size())},
IntegerType::get(executableBuilder.getContext(), 8)),
std::move(elfFile.getValue()));
// Add the binary to the parent hal.executable.
auto binaryOp = executableBuilder.create<IREE::HAL::ExecutableBinaryOp>(
variantOp.getLoc(), variantOp.sym_name(),
variantOp.target().getFormat(), bufferAttr);
binaryOp.mime_typeAttr(
executableBuilder.getStringAttr("application/x-elf"));
} else {
// Load the linked system library and optionally tag on the debug
// database. This debug database sits at the tail of the file and is
// ignored by system loaders and tools but still accessible to the runtime
// loader. Not all platforms have separate debug databases and need this.
auto libraryFileOr = linkArtifacts.libraryFile.read();
if (!libraryFileOr.hasValue()) {
return variantOp.emitError()
<< "failed to read back dylib temp file at "
<< linkArtifacts.libraryFile.path;
}
auto libraryFile = std::move(libraryFileOr).getValue();
if (options_.debugSymbols && linkArtifacts.debugFile.outputFile) {
if (failed(appendDebugDatabase(libraryFile, linkArtifacts.debugFile))) {
return variantOp.emitError()
<< "failed to append debug database to dylib file";
}
}
auto bufferAttr = DenseIntElementsAttr::get(
VectorType::get({static_cast<int64_t>(libraryFile.size())},
IntegerType::get(executableBuilder.getContext(), 8)),
std::move(libraryFile));
// Add the binary to the parent hal.executable.
auto binaryOp = executableBuilder.create<IREE::HAL::ExecutableBinaryOp>(
variantOp.getLoc(), variantOp.sym_name(),
variantOp.target().getFormat(), bufferAttr);
const char *mimeType = nullptr;
llvm::Triple targetTriple(options_.targetTriple);
switch (targetTriple.getObjectFormat()) {
case llvm::Triple::ObjectFormatType::COFF:
mimeType = "application/x-msdownload";
break;
case llvm::Triple::ObjectFormatType::ELF:
mimeType = "application/x-elf";
break;
case llvm::Triple::ObjectFormatType::MachO:
mimeType = "application/x-dylib";
break;
case llvm::Triple::ObjectFormatType::Wasm:
mimeType = "application/wasm";
break;
default:
mimeType = "application/octet-stream";
break;
}
binaryOp.mime_typeAttr(executableBuilder.getStringAttr(mimeType));
}
return success();
}
private:
ArrayAttr getExecutableTargets(MLIRContext *context) const {
SmallVector<Attribute> targetAttrs;
// This is where we would multiversion.
targetAttrs.push_back(getExecutableTarget(context));
return ArrayAttr::get(context, targetAttrs);
}
IREE::HAL::ExecutableTargetAttr getExecutableTarget(
MLIRContext *context) const {
std::string format;
if (options_.linkStatic) {
// Static libraries are just string references when serialized so we don't
// need to specify the target architecture.
format += "static";
} else {
// Construct the [loader]-[format]-[arch] triple.
llvm::Triple targetTriple(options_.targetTriple);
if (options_.linkEmbedded) {
// Using the IREE embedded ELF format/loader.
format += "embedded-elf-";
} else {
// System-specific shared library format.
format += "system-";
switch (targetTriple.getObjectFormat()) {
case llvm::Triple::ObjectFormatType::COFF:
format += "dll-";
break;
case llvm::Triple::ObjectFormatType::ELF:
format += "elf-";
break;
case llvm::Triple::ObjectFormatType::MachO:
format += "dylib-";
break;
case llvm::Triple::ObjectFormatType::Wasm:
format += "wasm-";
break;
default:
format += "unknown-";
break;
}
}
switch (targetTriple.getArch()) {
case llvm::Triple::ArchType::arm:
format += "arm_32";
break;
case llvm::Triple::ArchType::aarch64:
format += "arm_64";
break;
case llvm::Triple::ArchType::riscv32:
format += "riscv_32";
break;
case llvm::Triple::ArchType::riscv64:
format += "riscv_64";
break;
case llvm::Triple::ArchType::wasm32:
format += "wasm_32";
break;
case llvm::Triple::ArchType::wasm64:
format += "wasm_64";
break;
case llvm::Triple::ArchType::x86:
format += "x86_32";
break;
case llvm::Triple::ArchType::x86_64:
format += "x86_64";
break;
default:
format += "unknown";
break;
}
}
// Add some configurations to the `hal.executable.target` attribute.
SmallVector<NamedAttribute> config;
auto addConfig = [&](StringRef name, Attribute value) {
config.emplace_back(StringAttr::get(context, name), value);
};
// Set target triple.
addConfig("target_triple", StringAttr::get(context, options_.targetTriple));
// Set data layout
addConfig("data_layout", StringAttr::get(context, config_.dataLayoutStr));
// Set the native vector size. This creates a dummy llvm module just to
// build the TTI the right way.
addConfig("native_vector_size",
IntegerAttr::get(IndexType::get(context), config_.vectorSize));
// Set target CPU features.
addConfig("cpu_features",
StringAttr::get(context, options_.targetCPUFeatures));
return IREE::HAL::ExecutableTargetAttr::get(
context, StringAttr::get(context, "llvm"),
StringAttr::get(context, format), DictionaryAttr::get(context, config));
}
void initConfiguration() {
auto targetMachine = createTargetMachine(options_);
// Data layout
llvm::DataLayout DL = targetMachine->createDataLayout();
config_.dataLayoutStr = DL.getStringRepresentation();
// Set the native vector size. This creates a dummy llvm module just to
// build the TTI the right way.
llvm::LLVMContext llvmContext;
auto llvmModule =
std::make_unique<llvm::Module>("dummy_module", llvmContext);
llvm::Type *voidType = llvm::Type::getVoidTy(llvmContext);
llvmModule->setDataLayout(DL);
llvm::Function *dummyFunc = llvm::Function::Create(
llvm::FunctionType::get(voidType, false),
llvm::GlobalValue::ExternalLinkage, "dummy_func", *llvmModule);
llvm::TargetTransformInfo tti =
targetMachine->getTargetTransformInfo(*dummyFunc);
config_.vectorSize = tti.getRegisterBitWidth(
llvm::TargetTransformInfo::RGK_FixedWidthVector) /
8;
LLVM_DEBUG({
llvm::dbgs() << "CPU : " << targetMachine->getTargetCPU() << "\n";
llvm::dbgs() << "Target Triple : "
<< targetMachine->getTargetTriple().normalize() << "\n";
llvm::dbgs() << "Target Feature string : "
<< targetMachine->getTargetFeatureString() << "\n";
llvm::dbgs() << "Data Layout : " << config_.dataLayoutStr << "\n";
llvm::dbgs() << "Vector Width : " << config_.vectorSize << "\n";
});
}
LLVMTargetOptions options_;
// Additional target information besides that is contained in
// LLVMTargetOptions options_.
struct AdditionalConfigurationValues {
std::string dataLayoutStr;
int64_t vectorSize;
} config_;
};
void registerLLVMAOTTargetBackends(
std::function<LLVMTargetOptions()> queryOptions) {
getLLVMTargetOptionsFromFlags();
// Dynamically do preprocessor dispatch to initialize only targets that we
// care about if they are enabled. Unfortunately, the way the LLVM macros
// for this are set up and the inability to do a conditional within a macro
// means that we have to syntactically have a macro for every possible
// target we care about. There are more robust ways to do this but they all
// require build support, which is a pain to manage across platforms.
//
// See comments below.
#define LLVM_INITIALIZE_GENERIC(TargetName) \
LLVMInitialize##TargetName##Target(); \
LLVMInitialize##TargetName##TargetMC(); \
LLVMInitialize##TargetName##TargetInfo(); \
LLVMInitialize##TargetName##AsmPrinter(); \
LLVMInitialize##TargetName##AsmParser();
// CPU targets that we care about and have hard-linked against are here.
// They delegate to the generic initialize above. These must all be added
// to the build file or you will get undefined symbol errors at link time.
#define LLVM_INITIALIZE_TARGET_AArch64() LLVM_INITIALIZE_GENERIC(AArch64)
#define LLVM_INITIALIZE_TARGET_ARM() LLVM_INITIALIZE_GENERIC(ARM)
#define LLVM_INITIALIZE_TARGET_RISCV() LLVM_INITIALIZE_GENERIC(RISCV)
#define LLVM_INITIALIZE_TARGET_X86() LLVM_INITIALIZE_GENERIC(X86)
#define LLVM_INITIALIZE_TARGET_WebAssembly() \
LLVM_INITIALIZE_GENERIC(WebAssembly)
// We must no-op the name of each target we don't care about. This is annoying,
// but targets aren't created every day and isn't the end of the world. The
// error messages when missing are quite clear and you just add a line here.
#define LLVM_INITIALIZE_TARGET_AMDGPU()
#define LLVM_INITIALIZE_TARGET_AVR()
#define LLVM_INITIALIZE_TARGET_BPF()
#define LLVM_INITIALIZE_TARGET_Hexagon()
#define LLVM_INITIALIZE_TARGET_Lanai()
#define LLVM_INITIALIZE_TARGET_Mips()
#define LLVM_INITIALIZE_TARGET_MSP430()
#define LLVM_INITIALIZE_TARGET_NVPTX()
#define LLVM_INITIALIZE_TARGET_PowerPC()
#define LLVM_INITIALIZE_TARGET_Sparc()
#define LLVM_INITIALIZE_TARGET_SystemZ()
#define LLVM_INITIALIZE_TARGET_XCore()
#define LLVM_TARGET(TargetName) LLVM_INITIALIZE_TARGET_##TargetName()
#include "llvm/Config/Targets.def"
auto backendFactory = [=]() {
return std::make_shared<LLVMAOTTargetBackend>(queryOptions());
};
// #hal.device.target<"cpu", ...
static TargetBackendRegistration registration0("cpu", backendFactory);
// #hal.executable.target<"llvm", ...
static TargetBackendRegistration registration1("llvm", backendFactory);
// TODO(benvanik): remove legacy dylib name.
static TargetBackendRegistration registration2("dylib", backendFactory);
static TargetBackendRegistration registration3("dylib-llvm-aot",
backendFactory);
}
} // namespace HAL
} // namespace IREE
} // namespace iree_compiler
} // namespace mlir