blob: ee8e256321a53c126618d0215c86048287414738 [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 <cstdlib>
#include <unordered_set>
#include "compiler/plugins/target/LLVMCPU/Builtins/Device.h"
#include "compiler/plugins/target/LLVMCPU/Builtins/Musl.h"
#include "compiler/plugins/target/LLVMCPU/Builtins/UKernel.h"
#include "compiler/plugins/target/LLVMCPU/LLVMIRPasses.h"
#include "compiler/plugins/target/LLVMCPU/LLVMTargetOptions.h"
#include "compiler/plugins/target/LLVMCPU/LibraryBuilder.h"
#include "compiler/plugins/target/LLVMCPU/LinkerTool.h"
#include "compiler/plugins/target/LLVMCPU/StaticLibraryGenerator.h"
#include "iree/compiler/Codegen/Dialect/Codegen/IR/IREECodegenDialect.h"
#include "iree/compiler/Codegen/LLVMCPU/Passes.h"
#include "iree/compiler/Codegen/LLVMCPU/Utils.h"
#include "iree/compiler/Codegen/Utils/Utils.h"
#include "iree/compiler/Dialect/HAL/Target/Devices/LocalDevice.h"
#include "iree/compiler/Dialect/HAL/Target/TargetRegistry.h"
#include "iree/compiler/Dialect/HAL/Utils/LLVMLinkerUtils.h"
#include "iree/compiler/Dialect/LinalgExt/IR/LinalgExtDialect.h"
#include "iree/compiler/PluginAPI/Client.h"
#include "iree/compiler/Utils/ModuleUtils.h"
#include "llvm/Bitcode/BitcodeReader.h"
#include "llvm/Bitcode/BitcodeWriter.h"
#include "llvm/IR/GlobalValue.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/Linker/Linker.h"
#include "llvm/Support/TargetSelect.h"
#include "mlir/Dialect/ArmNeon/ArmNeonDialect.h"
#include "mlir/Dialect/ArmSME/IR/ArmSME.h"
#include "mlir/Dialect/ArmSVE/IR/ArmSVEDialect.h"
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
#include "mlir/Dialect/PDL/IR/PDL.h"
#include "mlir/Dialect/PDLInterp/IR/PDLInterp.h"
#include "mlir/Dialect/Transform/IR/TransformDialect.h"
#include "mlir/IR/BuiltinAttributes.h"
#include "mlir/IR/DialectResourceBlobManager.h"
#include "mlir/Target/LLVMIR/Dialect/ArmSME/ArmSMEToLLVMIRTranslation.h"
#include "mlir/Target/LLVMIR/Dialect/ArmSVE/ArmSVEToLLVMIRTranslation.h"
#include "mlir/Target/LLVMIR/Dialect/Builtin/BuiltinToLLVMIRTranslation.h"
#include "mlir/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.h"
#include "mlir/Target/LLVMIR/Export.h"
#define DEBUG_TYPE "iree-llvm-cpu-target"
using llvm::dbgs;
namespace mlir::iree_compiler::IREE::HAL {
static constexpr char kQueryFunctionName[] =
"iree_hal_executable_library_query";
static void dumpLLVMModuleToPath(StringRef path, StringRef baseName,
StringRef suffix, StringRef extPrefix,
llvm::Module &module) {
// Dump disassembly to path.
llvm::SmallVector<char> textData;
llvm::raw_svector_ostream textOstream(textData);
module.print(textOstream, nullptr);
std::string textExtension = extPrefix.str() + ".ll";
dumpDataToPath(path, baseName, suffix, textExtension,
StringRef(textData.data(), textData.size()));
// Dump bitcode to path.
llvm::SmallVector<char> binaryData;
llvm::raw_svector_ostream binaryOstream(binaryData);
// Write the specified module to the specified output stream.
llvm::WriteBitcodeToFile(module, binaryOstream);
std::string binaryExtension = extPrefix.str() + ".bc";
dumpDataToPath(path, baseName, suffix, binaryExtension,
StringRef(binaryData.data(), binaryData.size()));
}
static void fixupVisibility(llvm::Module &module,
const SetVector<llvm::Function *> &preserveFuncs) {
for (auto &func : module) {
if (preserveFuncs.contains(&func) || func.getName() == "iree_dll_main") {
// 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 : module.globals()) {
global.setDSOLocal(true);
global.setLinkage(llvm::GlobalValue::LinkageTypes::InternalLinkage);
}
}
// 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.has_value()) {
return failure();
}
auto debugFile = std::move(debugFileOr).value();
// 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();
}
class LLVMCPUTargetBackend final : public TargetBackend {
public:
explicit LLVMCPUTargetBackend(LLVMTargetOptions options)
: defaultOptions_(std::move(options)) {}
std::string getLegacyDefaultDeviceID() const override { return "llvm-cpu"; }
void getDefaultExecutableTargets(
MLIRContext *context, StringRef deviceID, DictionaryAttr deviceConfigAttr,
SmallVectorImpl<IREE::HAL::ExecutableTargetAttr> &executableTargetAttrs)
const override {
executableTargetAttrs.push_back(
getExecutableTarget(context, defaultOptions_.target));
}
void getHostExecutableTargets(MLIRContext *context, StringRef deviceID,
DictionaryAttr deviceConfigAttr,
SmallVectorImpl<IREE::HAL::ExecutableTargetAttr>
&executableTargetAttrs) const override {
std::optional<LLVMTarget> maybeTarget = LLVMTarget::createForHost();
if (maybeTarget) {
executableTargetAttrs.push_back(
getExecutableTarget(context, *maybeTarget));
}
}
IREE::HAL::ExecutableTargetAttr
getExecutableTarget(MLIRContext *context, const LLVMTarget &target) const {
// Add some configurations to the `hal.executable.target` attribute.
Builder b(context);
SmallVector<NamedAttribute> configItems;
target.storeToConfigAttrs(context, configItems);
// Compute the format used at runtime to select the executable loader.
std::string format;
if (target.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(target.getTriple());
if (target.getLinkEmbedded()) {
// 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;
}
}
format += getIreeArchNameForTargetTriple(targetTriple);
}
return b.getAttr<IREE::HAL::ExecutableTargetAttr>(
b.getStringAttr("llvm-cpu"), b.getStringAttr(format),
b.getDictionaryAttr(configItems));
}
void getDependentDialects(DialectRegistry &registry) const override {
mlir::registerBuiltinDialectTranslation(registry);
mlir::registerLLVMDialectTranslation(registry);
mlir::registerArmSMEDialectTranslation(registry);
mlir::registerArmSVEDialectTranslation(registry);
// TODO: make inclusion of ArmNeon conditional?
// clang-format off
registry.insert<IREE::Codegen::IREECodegenDialect,
IREE::LinalgExt::IREELinalgExtDialect,
mlir::transform::TransformDialect,
pdl::PDLDialect,
pdl_interp::PDLInterpDialect,
arm_neon::ArmNeonDialect,
arm_sme::ArmSMEDialect,
arm_sve::ArmSVEDialect>();
// clang-format on
}
void
buildConfigurationPassPipeline(IREE::HAL::ExecutableTargetAttr targetAttr,
OpPassManager &passManager) override {
buildLLVMCPUCodegenConfigurationPassPipeline(passManager);
}
void buildTranslationPassPipeline(IREE::HAL::ExecutableTargetAttr targetAttr,
OpPassManager &passManager) override {
bool enableAArch64SME = isAArch64(targetAttr) && hasSMEFeature(targetAttr);
buildLLVMCPUCodegenPassPipeline(passManager, enableAArch64SME);
}
void buildLinkingPassPipeline(OpPassManager &passManager) override {
buildLLVMCPULinkingPassPipeline(passManager, "llvm-cpu");
}
// Gets the LLVM target from |variantOp|.
// This will differ from the default options specified by command line flags
// whenever multi-targeting.
// Returns none and emits on failure.
std::optional<LLVMTarget>
getVariantTarget(IREE::HAL::ExecutableVariantOp variantOp) {
auto configAttr = variantOp.getTarget().getConfiguration();
return LLVMTarget::loadFromConfigAttr(variantOp.getLoc(), configAttr,
defaultOptions_.target);
}
LogicalResult serializeExecutable(const SerializationOptions &options,
IREE::HAL::ExecutableVariantOp variantOp,
OpBuilder &executableBuilder) override {
// Perform the translation in a separate context to avoid any
// multi-threading issues.
llvm::LLVMContext context;
auto maybeTarget = getVariantTarget(variantOp);
if (!maybeTarget)
return failure();
const LLVMTarget &target = *maybeTarget;
LLVM_DEBUG(dbgs() << "LLVM-CPU SerializeExecutable:\n"
<< "-----------------------------\n";
target.print(dbgs()));
// For debugging effective options in live builds, uncomment the following.
// dbgs() << "LLVM-CPU ";
// target.print(dbgs());
// 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 (target.getLinkEmbedded() && target.linkStatic) {
return variantOp.emitError()
<< "cannot embed ELF and produce static library simultaneously";
}
// Try to create the LLVM target machine interface for the variant target.
auto targetMachine = createTargetMachine(target);
if (!targetMachine) {
return mlir::emitError(variantOp.getLoc())
<< "failed to create target machine for target triple '"
<< target.getTriple() << "'";
}
// 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.
const llvm::Triple &targetTriple = targetMachine->getTargetTriple();
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/iree-org/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 =
target.debugSymbols ? LibraryBuilder::Mode::INCLUDE_REFLECTION_ATTRS
: LibraryBuilder::Mode::NONE;
LibraryBuilder libraryBuilder(llvmModule.get(), libraryBuilderMode,
LibraryBuilder::Version::LATEST);
switch (target.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;
case SanitizerKind::kThread: {
libraryBuilder.setSanitizerKind(LibraryBuilder::SanitizerKind::THREAD);
for (auto &function : llvmModule->getFunctionList()) {
function.addFnAttr(llvm::Attribute::SanitizeThread);
}
} break;
}
// Declare dynamically imported functions.
auto importsAttrName =
StringAttr::get(variantOp.getContext(), "hal.executable.imports");
if (auto importsAttr =
variantOp->getAttrOfType<ArrayAttr>(importsAttrName)) {
for (auto importAttr : importsAttr.getAsValueRange<ArrayAttr>()) {
auto nameAttr = llvm::cast<StringAttr>(importAttr[0]);
auto weakAttr = llvm::cast<BoolAttr>(importAttr[1]);
libraryBuilder.addImport(nameAttr.getValue(), weakAttr.getValue());
}
variantOp->removeAttr(importsAttrName);
}
// Declare exported entry points.
auto align16 = llvm::Attribute::getWithAlignment(context, llvm::Align(16));
for (auto exportOp : variantOp.getBlock().getOps<ExecutableExportOp>()) {
// Find the matching function in the LLVM module.
auto *llvmFunc = llvmModule->getFunction(exportOp.getName());
if (!llvmFunc)
continue;
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::NonNull);
llvmFunc->addParamAttr(i, llvm::Attribute::NoAlias);
llvmFunc->addParamAttr(i, align16);
}
LibraryBuilder::DispatchAttrs dispatchAttrs = {0};
// Entry points may optionally 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.
dispatchAttrs.localMemorySize = exportOp.getWorkgroupLocalMemory()
.value_or(APInt(64, 0))
.getSExtValue();
// Specify the constant and binding information used to validate
// dispatches.
if (auto layoutAttr = exportOp.getLayout()) {
dispatchAttrs.constantCount = layoutAttr.getConstants();
dispatchAttrs.bindingCount = layoutAttr.getBindings().size();
}
LibraryBuilder::SourceLocation sourceLocation;
if (options.debugLevel >= 1) {
if (auto loc = findFirstFileLoc(exportOp.getLoc())) {
sourceLocation = {"", loc->getFilename().str(), loc->getLine()};
}
}
SmallVector<LibraryBuilder::SourceLocation> stageLocations;
if (options.debugLevel >= 3) {
if (auto locsAttr = exportOp.getSourceLocsAttr()) {
for (auto locAttr : locsAttr.getValue()) {
if (auto loc =
findFirstFileLoc(cast<LocationAttr>(locAttr.getValue()))) {
stageLocations.push_back({
locAttr.getName().str(),
loc->getFilename().str(),
loc->getLine(),
});
}
}
}
}
libraryBuilder.addExport(exportOp.getName(), std::move(sourceLocation),
std::move(stageLocations), /*tag=*/"",
dispatchAttrs, llvmFunc);
}
// Embed source files (if present).
if (auto sourcesAttr = variantOp.getSourcesAttr()) {
for (auto sourceAttr : sourcesAttr.getValue()) {
if (auto resourceAttr = dyn_cast_if_present<DenseResourceElementsAttr>(
sourceAttr.getValue())) {
auto handle = resourceAttr.getRawHandle();
SmallVector<char> rawData;
llvm::append_range(rawData, handle.getBlob()->getData());
libraryBuilder.addSourceFile(sourceAttr.getName(),
std::move(rawData));
}
}
}
auto queryFunctionName = std::string(kQueryFunctionName);
if (target.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->setDSOLocal(false);
queryLibraryFunc->setVisibility(
llvm::GlobalValue::VisibilityTypes::DefaultVisibility);
queryLibraryFunc->setLinkage(
llvm::GlobalValue::LinkageTypes::ExternalLinkage);
queryLibraryFunc->setDLLStorageClass(
llvm::GlobalValue::DLLStorageClassTypes::DLLExportStorageClass);
// If linking dynamically, find a suitable linker tool and configure the
// module with any options that tool requires.
std::unique_ptr<LinkerTool> linkerTool;
if (!target.linkStatic) {
// Grab a linker tool based on the options (and target environment).
// This uses the defaultOptions_ in order to get paths and such, which
// are environmental, but replace the target with the actual one.
LLVMTargetOptions options = defaultOptions_;
options.target = target;
linkerTool = LinkerTool::getForTarget(targetTriple, options);
if (!linkerTool) {
return mlir::emitError(variantOp.getLoc())
<< "failed to find a target linker for the given target triple '"
<< targetTriple.str() << "'";
}
// 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.
llvmModule->setDataLayout(targetMachine->createDataLayout());
llvmModule->setTargetTriple(targetMachine->getTargetTriple().str());
// Dump just the codegen bitcode before linking and optimization.
if (!options.dumpIntermediatesPath.empty()) {
dumpLLVMModuleToPath(options.dumpIntermediatesPath, options.dumpBaseName,
variantOp.getName(), ".codegen", *llvmModule);
}
// Statically link libraries into our module prior to LLVM optimizations.
// This approximates LTO.
llvm::Linker moduleLinker(*llvmModule);
// Link any bitcode files specified on the command line.
if (failed(linkCmdlineBitcodeFiles(variantOp.getLoc(), moduleLinker,
llvm::Linker::OverrideFromSrc,
*targetMachine, context))) {
return failure();
}
// Link any bitcode objects specified in executable.object attributes and
// specialize them for the current config.
if (failed(linkBitcodeObjects(variantOp.getLoc(), moduleLinker,
llvm::Linker::LinkOnlyNeeded, *targetMachine,
variantOp.getObjectsAttr(), context))) {
return failure();
}
// Link our libdevice after all codegen and user objects as they may
// reference it. Some of the functions in here are only known used after
// we perform LLVM ISel and need to be pulled in whether they are used or
// not.
if (failed(linkBitcodeModule(
variantOp.getLoc(), moduleLinker, llvm::Linker::OverrideFromSrc,
*targetMachine, "libdevice",
loadDeviceBitcode(targetMachine.get(), context),
[&](llvm::Module &module) {
specializeDeviceModule(variantOp, module, *targetMachine);
}))) {
return mlir::emitError(variantOp.getLoc())
<< "failed linking in builtin library for target triple '"
<< targetTriple.str() << "'";
}
if (target.getLinkEmbedded()) {
// Link musl last and pull in all of it - this is sad but LLVM will take
// IR intrinsics and generate calls out to libc during code generation and
// we have no control over that - if we don't provide the symbols here
// then linking with ld will fail.
if (failed(linkBitcodeModule(
variantOp.getLoc(), moduleLinker, llvm::Linker::OverrideFromSrc,
*targetMachine, "libmusl",
loadMuslBitcode(targetMachine.get(), context)))) {
return mlir::emitError(variantOp.getLoc())
<< "failed linking in builtin library for target triple '"
<< targetTriple.str() << "'";
}
}
if (target.linkUkernelBitcode) {
// Link in ukernel bitcode.
if (hasUkernel(variantOp.getTarget())) {
llvm::Expected<std::unique_ptr<llvm::Module>> bitcode =
loadUKernelBitcode(targetMachine.get(), context);
if (!bitcode) {
return mlir::emitError(variantOp.getLoc())
<< "failed to load ukernel bitcode: "
<< llvm::toString(bitcode.takeError());
}
if (bitcode.get()) {
StringRef bitcodeName = bitcode.get()->getName();
if (failed(linkBitcodeModule(variantOp.getLoc(), moduleLinker,
llvm::Linker::LinkOnlyNeeded,
*targetMachine, bitcodeName,
std::move(bitcode), {}))) {
return mlir::emitError(variantOp.getLoc())
<< "failed linking in architecture-specific ukernel bitcode "
"for target triple '"
<< targetTriple.str() << "'";
}
}
}
}
// 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();
// Dump all linked bitcode prior to optimization.
if (!options.dumpIntermediatesPath.empty()) {
dumpLLVMModuleToPath(options.dumpIntermediatesPath, options.dumpBaseName,
variantOp.getName(), ".linked", *llvmModule);
}
// LLVM opt passes that perform code generation optimizations/transformation
// similar to what a frontend would do.
if (failed(
runLLVMIRPasses(target, targetMachine.get(), llvmModule.get()))) {
return variantOp.emitError()
<< "failed to run LLVM-IR opt passes for IREE::HAL::ExecutableOp "
"targeting '"
<< targetTriple.str() << "'";
}
// Fixup visibility from any symbols we may link in - we want to hide all
// but the query entry point.
// Note: can't move this before runLLVMIRPasses at the moment, as further
// symbol references may still be created past this point, namely to math
// functions, e.g. `llvm.frem` lowering to a call to `fmodf`.
SetVector<llvm::Function *> preservedFuncs;
preservedFuncs.insert(queryLibraryFunc);
fixupVisibility(*llvmModule, preservedFuncs);
// Dump bitcode post-linking and optimization.
if (!options.dumpIntermediatesPath.empty()) {
dumpLLVMModuleToPath(options.dumpIntermediatesPath, options.dumpBaseName,
variantOp.getName(), ".optimized", *llvmModule);
}
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::CodeGenFileType::ObjectFile,
&objectData))) {
return variantOp.emitError()
<< "failed to compile LLVM-IR module to an object file";
}
if (!options.dumpIntermediatesPath.empty()) {
dumpDataToPath(options.dumpIntermediatesPath, options.dumpBaseName,
variantOp.getName(), ".o", objectData);
}
auto objectFile = Artifact::createTemporary(libraryName, "o");
auto &os = objectFile.outputFile->os();
os << objectData;
os.flush();
os.close();
objectFiles.push_back(std::move(objectFile));
}
// Dump assembly listing after optimization, which is just a textual
// representation of the object file we generate below.
if (!options.dumpIntermediatesPath.empty()) {
std::string asmData;
if (failed(runEmitObjFilePasses(targetMachine.get(), llvmModule.get(),
llvm::CodeGenFileType::AssemblyFile,
&asmData))) {
return variantOp.emitError()
<< "failed to compile LLVM-IR module to an assembly file";
}
dumpDataToPath(options.dumpIntermediatesPath, options.dumpBaseName,
variantOp.getName(), ".s", asmData);
}
// If custom object files were specified then add those to our artifact set.
// These will either be combined into the resulting static library or linked
// statically into the resulting dynamic library.
SmallVector<IREE::HAL::ExecutableObjectAttr> linkerObjectAttrs;
IREE::HAL::ExecutableObjectAttr::filterObjects(variantOp.getObjectsAttr(),
{".o", ".obj", ".a", ".lib"},
linkerObjectAttrs);
for (auto [index, attr] : llvm::enumerate(linkerObjectAttrs)) {
auto objectAttr = llvm::cast<IREE::HAL::ExecutableObjectAttr>(attr);
if (auto dataAttr = objectAttr.getData()) {
objectFiles.push_back(Artifact::createTemporary(
objectFiles.front().path + "_object_" + std::to_string(index),
llvm::sys::path::extension(objectAttr.getPath())));
} else {
auto absolutePath = objectAttr.getAbsolutePath();
if (failed(absolutePath)) {
llvm::errs()
<< "ERROR: referenced object file not found on any path; use "
"--iree-hal-executable-object-search-path= to add search "
"paths: "
<< objectAttr << "\n";
return failure();
}
objectFiles.push_back(Artifact::fromFile(*absolutePath));
}
}
if (target.linkStatic) {
return serializeStaticLibraryExecutable(options, target, variantOp,
executableBuilder, libraryName,
queryFunctionName, objectFiles);
} else {
return serializeDynamicLibraryExecutable(
options, target, variantOp, executableBuilder, libraryName,
targetTriple, objectFiles, linkerTool.get());
}
}
LogicalResult serializeStaticLibraryExecutable(
const SerializationOptions &options, const LLVMTarget &target,
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,
target.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.getSymName(), "static",
libraryNameVector);
return success();
}
LogicalResult serializeDynamicLibraryExecutable(
const SerializationOptions &options, const LLVMTarget &target,
IREE::HAL::ExecutableVariantOp variantOp, OpBuilder &executableBuilder,
const std::string &libraryName, const llvm::Triple &targetTriple,
const SmallVector<Artifact> &objectFiles, LinkerTool *linkerTool) {
// Link the generated object files into a dylib.
auto linkArtifactsOr =
linkerTool->linkDynamicLibrary(libraryName, objectFiles);
if (!linkArtifactsOr.has_value()) {
return mlir::emitError(variantOp.getLoc())
<< "failed to link executable and generate target dylib (check "
"above for more specific error messages)";
}
auto &linkArtifacts = linkArtifactsOr.value();
if (defaultOptions_.keepLinkerArtifacts) {
mlir::emitRemark(variantOp.getLoc())
<< "linker artifacts for " << variantOp.getName() << " preserved:\n"
<< " " << linkArtifacts.libraryFile.path;
linkArtifacts.keepAllFiles();
for (auto &objectFile : objectFiles) {
objectFile.keep();
}
}
if (target.getLinkEmbedded()) {
// Load the linked ELF file and pack into an attr.
auto elfFile = linkArtifacts.libraryFile.read();
if (!elfFile.has_value()) {
return variantOp.emitError()
<< "failed to read back dylib temp file at "
<< linkArtifacts.libraryFile.path;
}
if (!options.dumpBinariesPath.empty()) {
dumpDataToPath<int8_t>(options.dumpBinariesPath, options.dumpBaseName,
variantOp.getName(), ".so", *elfFile);
}
auto bufferAttr = DenseIntElementsAttr::get(
VectorType::get({static_cast<int64_t>(elfFile->size())},
IntegerType::get(executableBuilder.getContext(), 8)),
std::move(elfFile.value()));
// Add the binary to the parent hal.executable.
auto binaryOp = executableBuilder.create<IREE::HAL::ExecutableBinaryOp>(
variantOp.getLoc(), variantOp.getSymName(),
variantOp.getTarget().getFormat(), bufferAttr);
binaryOp.setMimeTypeAttr(
executableBuilder.getStringAttr("application/x-elf"));
} else {
const char *mimeType = nullptr;
const char *extension = "";
switch (targetTriple.getObjectFormat()) {
case llvm::Triple::ObjectFormatType::COFF:
mimeType = "application/x-msdownload";
extension = ".dll";
break;
case llvm::Triple::ObjectFormatType::ELF:
mimeType = "application/x-elf";
extension = ".so";
break;
case llvm::Triple::ObjectFormatType::MachO:
mimeType = "application/x-dylib";
extension = ".dylib";
break;
case llvm::Triple::ObjectFormatType::Wasm:
mimeType = "application/wasm";
extension = ".wasm";
break;
default:
mimeType = "application/octet-stream";
break;
}
// 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.has_value()) {
return variantOp.emitError()
<< "failed to read back dylib temp file at "
<< linkArtifacts.libraryFile.path;
}
auto libraryFile = std::move(libraryFileOr).value();
if (target.debugSymbols && linkArtifacts.debugFile.outputFile) {
if (failed(appendDebugDatabase(libraryFile, linkArtifacts.debugFile))) {
return variantOp.emitError()
<< "failed to append debug database to dylib file";
}
}
if (!options.dumpBinariesPath.empty()) {
dumpDataToPath<int8_t>(options.dumpBinariesPath, options.dumpBaseName,
variantOp.getName(), extension, libraryFile);
}
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.getSymName(),
variantOp.getTarget().getFormat(), bufferAttr);
binaryOp.setMimeTypeAttr(executableBuilder.getStringAttr(mimeType));
}
return success();
}
private:
// Default options as registered from the command line. Should not be
// relied on outside of getDefaultDeviceTarget() since it represents
// a static "cross compiling" config and would override more specific
// settings.
const LLVMTargetOptions defaultOptions_;
};
struct LLVMCPUSession
: public PluginSession<LLVMCPUSession, LLVMCPUTargetCLOptions,
PluginActivationPolicy::DefaultActivated> {
void populateHALTargetDevices(IREE::HAL::TargetDeviceList &targets) {
// TODO(multi-device): move local device registration out.
// This exists here for backwards compat with the old
// iree-hal-target-backends flag that needs to look up the device by backend
// name.
// #hal.device.target<"llvm-cpu", ...
targets.add("llvm-cpu", [=]() {
LocalDevice::Options localDeviceOptions;
localDeviceOptions.defaultTargetBackends.push_back("llvm-cpu");
localDeviceOptions.defaultHostBackends.push_back("llvm-cpu");
return std::make_shared<LocalDevice>(localDeviceOptions);
});
}
void populateHALTargetBackends(IREE::HAL::TargetBackendList &targets) {
// #hal.executable.target<"llvm-cpu", ...
targets.add("llvm-cpu", [=]() {
return std::make_shared<LLVMCPUTargetBackend>(options.getTargetOptions());
});
}
};
} // namespace mlir::iree_compiler::IREE::HAL
IREE_DEFINE_COMPILER_OPTION_FLAGS(
mlir::iree_compiler::IREE::HAL::LLVMCPUTargetCLOptions);
extern "C" bool iree_register_compiler_plugin_hal_target_llvm_cpu(
mlir::iree_compiler::PluginRegistrar *registrar) {
registrar->registerPlugin<mlir::iree_compiler::IREE::HAL::LLVMCPUSession>(
"hal_target_llvm_cpu");
return true;
}