blob: 4892e4438e35fd0c5fbd7f73198a4122cbf9bf7c [file]
// Copyright 2024 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 <memory>
#include <utility>
#include "iree/compiler/Dialect/HAL/IR/HALDialect.h"
#include "iree/compiler/Dialect/HAL/IR/HALOps.h"
#include "iree/compiler/Dialect/HAL/Target/TargetBackend.h"
#include "iree/compiler/Dialect/HAL/Target/TargetRegistry.h"
#include "iree/compiler/Dialect/HAL/Transforms/Passes.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/raw_ostream.h"
#include "mlir/AsmParser/AsmParser.h"
#include "mlir/IR/Attributes.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/Diagnostics.h"
#include "mlir/Pass/Pass.h"
namespace mlir::iree_compiler::IREE::HAL {
#define GEN_PASS_DEF_ASSIGNTARGETDEVICESPASS
#include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc"
namespace {
//===----------------------------------------------------------------------===//
// --iree-hal-assign-target-devices
//===----------------------------------------------------------------------===//
// Strips leading and trailing whitespace from |value|.
static StringRef stripWhitespace(StringRef value) {
while (!value.empty() && llvm::isSpace(value.front())) {
value = value.drop_front(1);
}
while (!value.empty() && llvm::isSpace(value.back())) {
value = value.drop_back(1);
}
return value;
}
// Strips leading and trailing double quotes from |value| if both exist.
static StringRef stripQuotes(StringRef value) {
value = stripWhitespace(value);
StringRef unquoted = value;
if (unquoted.consume_front("\"") && unquoted.consume_back("\"")) {
return stripWhitespace(unquoted);
}
return value;
}
// Consumes a leading `name=` literal.
// Returns the `name` and leaves remaining characters after `=` in |value|.
// Returns an empty string if no name literal is present.
static StringRef consumeNameLiteral(StringRef &value) {
value = stripWhitespace(value);
const size_t splitIdx = value.find('=');
if (splitIdx == std::string::npos) {
return "";
}
for (size_t i = 0; i < splitIdx; ++i) {
const char c = value[i];
if (!llvm::isAlnum(c) && c != '_') {
return value;
}
}
const StringRef name = value.substr(0, splitIdx);
value = stripWhitespace(value.substr(splitIdx + 1));
return stripWhitespace(name);
}
// Consumes the first portion of |value| corresponding to a device alias.
// Expects: `abc` or `abc[123]` (and allows `"abc"[123]`).
// Only valid literals will be parsed (a-z0-9_).
// Returns the device ID and optional ordinal. All other unconsumed characters
// will remain in |value| upon return.
static std::pair<StringRef, std::optional<int64_t>>
consumeAliasLiteral(StringRef &value) {
value = stripWhitespace(value);
const size_t splitIdx = value.find(',');
StringRef part =
splitIdx == std::string::npos ? value : value.substr(0, splitIdx);
StringRef deviceID = part;
std::optional<int64_t> ordinal;
const size_t ordinalIdx = part.find('[');
if (ordinalIdx != std::string::npos) {
deviceID = part.substr(0, ordinalIdx);
StringRef ordinalStr = part.substr(ordinalIdx + 1);
APInt ordinalInt;
if (!ordinalStr.consumeInteger(10, ordinalInt)) {
ordinal = ordinalInt.getSExtValue();
}
}
value = stripWhitespace(value.substr(part.size()));
return std::make_pair(stripQuotes(deviceID), ordinal);
}
struct TargetSpec {
StringAttr name;
TypedAttr attr;
};
// Parses the user-provided string into a target spec.
//
// Supports attributes:
// #hal.device.alias<...>
// #hal.device.target<...>
// #hal.device.select<...>
// #hal.device.fallback<...>
// Supports convenience shorthand:
// ...,... -> #hal.device.select<[...,...]>
// target -> #hal.device.alias<"target">
// target[0] -> #hal.device.alias<"target"[0]>
// "target"[0] -> #hal.device.alias<"target"[0]>
// Supports name= prefixes:
// name=... -> ...
static FailureOr<TargetSpec> parseTargetSpec(Location loc,
StringRef targetSpecStr) {
auto *context = loc.getContext();
targetSpecStr = stripQuotes(targetSpecStr);
// Check for a name prefix and strip it from the spec.
StringRef name = consumeNameLiteral(targetSpecStr);
StringAttr nameAttr =
name.empty() ? StringAttr{} : StringAttr::get(context, name);
// Parse the spec attributes.
SmallVector<Attribute> attrs;
while (!targetSpecStr.empty()) {
TypedAttr typedAttr;
if (targetSpecStr.starts_with('#')) {
// MLIR attribute.
size_t numRead = 0;
auto parsedAttr = mlir::parseAttribute(targetSpecStr, context,
/*type=*/nullptr, &numRead);
if (!parsedAttr) {
return mlir::emitError(loc) << "failed to parse target spec prefix `"
<< targetSpecStr << "`";
}
typedAttr = dyn_cast<TypedAttr>(parsedAttr);
if (!typedAttr) {
return mlir::emitError(loc) << "unexpected target attribute type: "
"expected a `!hal.device` but got `"
<< parsedAttr << "`";
}
targetSpecStr = stripWhitespace(targetSpecStr.substr(numRead));
} else {
// Alias string.
auto [deviceID, ordinal] = consumeAliasLiteral(targetSpecStr);
typedAttr = IREE::HAL::DeviceAliasAttr::get(
context, IREE::HAL::DeviceType::get(context),
StringAttr::get(context, deviceID), ordinal, DictionaryAttr{});
}
if (!typedAttr || !isa<IREE::HAL::DeviceType>(typedAttr.getType())) {
return mlir::emitError(loc) << "unexpected target attribute type: "
"expected a `!hal.device` but got `"
<< typedAttr.getType() << "`";
}
attrs.push_back(typedAttr);
if (targetSpecStr.empty()) {
break; // done
} else if (!targetSpecStr.starts_with(',')) {
return mlir::emitError(loc)
<< "unexpected additional characters after parsing an element: `"
<< targetSpecStr << "`";
}
targetSpecStr = targetSpecStr.substr(1); // strip ,
}
if (attrs.empty()) {
return mlir::emitError(loc) << "expected one or more target attributes";
} else if (attrs.size() == 1) {
return TargetSpec{nameAttr, cast<TypedAttr>(attrs.front())};
} else {
return TargetSpec{nameAttr,
IREE::HAL::DeviceSelectAttr::get(context, attrs)};
}
}
struct AssignTargetDevicesPass
: public IREE::HAL::impl::AssignTargetDevicesPassBase<
AssignTargetDevicesPass> {
using IREE::HAL::impl::AssignTargetDevicesPassBase<
AssignTargetDevicesPass>::AssignTargetDevicesPassBase;
void runOnOperation() override {
auto moduleOp = getOperation();
// If no targets are specified we can't do anything - another pass earlier
// in the pipeline will have had to add the targets.
if (targetDevices.empty()) {
return;
}
// Check to see if targets are already specified and if so then no-op the
// pass so that we don't mess with whatever the user intended.
if (moduleOp->hasAttr("hal.device.targets")) {
return;
}
// If there are any device globals declared then bail as it means the user
// has already materialized the devices they want.
for (auto globalOp : moduleOp.getOps<IREE::Util::GlobalOpInterface>()) {
if (isa<IREE::HAL::DeviceType>(globalOp.getGlobalType())) {
return;
}
}
// Parse each spec and validate correctness.
bool hasAnyNamed = false;
bool hasAnyUnnamed = false;
SmallVector<TargetSpec> targetSpecs;
for (auto &targetDevice : targetDevices) {
auto targetSpecOr = parseTargetSpec(moduleOp.getLoc(), targetDevice);
if (failed(targetSpecOr)) {
return signalPassFailure();
}
if (targetSpecOr->name) {
hasAnyNamed = true;
} else {
hasAnyUnnamed = true;
}
targetSpecs.push_back(*targetSpecOr);
}
// If any spec has a name assigned then all must have names assigned.
if (hasAnyNamed && hasAnyUnnamed) {
emitError(moduleOp.getLoc())
<< "if any target device spec has a name then all must be named";
return signalPassFailure();
}
if (hasAnyNamed) {
// NOTE: we allow duplicate names to override assignment.
llvm::MapVector<StringAttr, Attribute> deviceAttrMap;
for (auto targetSpec : targetSpecs) {
assert(targetSpec.name && "all devices must be named");
deviceAttrMap[targetSpec.name] = targetSpec.attr;
}
SmallVector<NamedAttribute> deviceAttrs;
for (auto [name, value] : deviceAttrMap) {
deviceAttrs.push_back(NamedAttribute(name, value));
}
moduleOp->setAttr(
"hal.device.targets",
DictionaryAttr::get(moduleOp.getContext(), deviceAttrs));
} else {
SmallVector<Attribute> deviceAttrs;
for (auto [name, value] : targetSpecs) {
assert(!name && "no devices may have names");
deviceAttrs.push_back(value);
}
moduleOp->setAttr("hal.device.targets",
ArrayAttr::get(moduleOp.getContext(), deviceAttrs));
}
}
};
} // namespace
} // namespace mlir::iree_compiler::IREE::HAL