blob: 096b7bf643c49a78f23a09e9be39964f6864d2f9 [file]
// 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 <utility>
#include "iree/compiler/Dialect/HAL/Analysis/DeviceAnalysis.h"
#include "iree/compiler/Dialect/HAL/IR/HALDialect.h"
#include "iree/compiler/Dialect/HAL/IR/HALOps.h"
#include "iree/compiler/Dialect/HAL/Transforms/Passes.h"
#include "iree/compiler/Dialect/Util/IR/UtilDialect.h"
#include "iree/compiler/Utils/StringUtils.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_MEMOIZEDEVICEQUERIESPASS
#include "iree/compiler/Dialect/HAL/Transforms/Passes.h.inc"
namespace {
//===----------------------------------------------------------------------===//
// --iree-hal-memoize-device-queries
//===----------------------------------------------------------------------===//
// All queries for a particular !hal.device global.
struct DeviceQueries {
// Global !hal.device.
IREE::Util::GlobalOpInterface deviceOp;
// [category, key, default] used for lookup/indexing.
SmallVector<Attribute> queryKeys;
// Ops performing queries against the device by [category, key, default].
DenseMap<Attribute, SmallVector<IREE::HAL::DeviceQueryOp>> queryOps;
};
// A query being replaced by global lookups.
struct Query {
Query(Location loc) : loc(loc) {}
Location loc;
IREE::Util::GlobalOp okGlobalOp;
IREE::Util::GlobalOp valueGlobalOp;
StringAttr categoryAttr;
StringAttr keyAttr;
TypedAttr defaultValueAttr;
};
static std::string getDeviceNamePrefix(IREE::Util::GlobalOpInterface deviceOp) {
StringRef deviceName = deviceOp.getGlobalName().getValue();
if (deviceName.starts_with("__")) {
return deviceName.str();
}
auto prefixedName = "__" + deviceName;
return prefixedName.str();
}
// NOTE: this implementation is just for a single active device. As we start to
// support multiple devices we'll need to change this to be per-device.
struct MemoizeDeviceQueriesPass
: public IREE::HAL::impl::MemoizeDeviceQueriesPassBase<
MemoizeDeviceQueriesPass> {
void runOnOperation() override {
auto moduleOp = getOperation();
// Analyze the module to determine which devices are used where.
DeviceAnalysis deviceAnalysis(moduleOp);
if (failed(deviceAnalysis.run())) {
return signalPassFailure();
}
// Prepare device table indexed by symbol name.
DenseMap<Attribute, DeviceQueries> allDeviceQueries;
for (auto deviceOp : deviceAnalysis.getDeviceGlobals()) {
allDeviceQueries[deviceOp.getGlobalName()].deviceOp = deviceOp;
}
// Find all query ops we want to memoize and group them together.
// This lets us easily replace all usages of a match with a single variable.
for (auto callableOp : moduleOp.getOps<mlir::CallableOpInterface>()) {
callableOp.walk([&](IREE::HAL::DeviceQueryOp queryOp) {
// Try to find the device this query is made on. If analysis failed then
// we can't memoize the query today.
auto deviceGlobals =
deviceAnalysis.lookupDeviceGlobals(queryOp.getDevice());
if (!deviceGlobals || deviceGlobals->size() != 1)
return WalkResult::advance();
IREE::Util::GlobalOpInterface deviceGlobalOp = deviceGlobals->front();
// Construct key used to dedupe/lookup the query.
auto fullKey = ArrayAttr::get(
moduleOp.getContext(),
{
StringAttr::get(moduleOp.getContext(),
queryOp.getCategory() + queryOp.getKey()),
queryOp.getDefaultValue().has_value()
? queryOp.getDefaultValueAttr()
: Attribute{},
});
// Track the query on the device.
auto &deviceQueries = allDeviceQueries[deviceGlobalOp.getGlobalName()];
auto lookup = deviceQueries.queryOps.try_emplace(
fullKey, SmallVector<IREE::HAL::DeviceQueryOp>{});
if (lookup.second) {
deviceQueries.queryKeys.push_back(std::move(fullKey));
}
lookup.first->second.push_back(queryOp);
return WalkResult::advance();
});
}
// Create each query variable and replace the uses with loads.
SymbolTable symbolTable(moduleOp);
auto moduleBuilder = OpBuilder::atBlockBegin(moduleOp.getBody());
for (auto deviceOp : deviceAnalysis.getDeviceGlobals()) {
auto &deviceQueries = allDeviceQueries[deviceOp.getGlobalName()];
if (deviceQueries.queryKeys.empty()) {
// No queries against this device.
continue;
}
// Create one global per unique query key against the device.
SmallVector<Query> queries;
moduleBuilder.setInsertionPointAfter(deviceOp);
for (auto [i, queryKey] : llvm::enumerate(deviceQueries.queryKeys)) {
auto &queryOps = deviceQueries.queryOps[queryKey];
auto queryLoc = moduleBuilder.getFusedLoc(llvm::map_to_vector(
queryOps, [&](auto queryOp) { return queryOp.getLoc(); }));
// Create a global for the ok flag and the queried value.
// TODO(benvanik): create a better name based on the key.
auto anyQueryOp = queryOps.front();
auto queryType = anyQueryOp.getValue().getType();
std::string variableName =
getDeviceNamePrefix(deviceOp) + "_query_" + std::to_string(i) +
"_" + sanitizeSymbolName(anyQueryOp.getCategory()) + "_" +
sanitizeSymbolName(anyQueryOp.getKey());
auto okGlobalOp = moduleBuilder.create<IREE::Util::GlobalOp>(
queryLoc, variableName + "_ok",
/*isMutable=*/false, moduleBuilder.getI1Type());
symbolTable.insert(okGlobalOp);
okGlobalOp.setPrivate();
auto valueGlobalOp = moduleBuilder.create<IREE::Util::GlobalOp>(
queryLoc, variableName,
/*isMutable=*/false, queryType);
symbolTable.insert(valueGlobalOp);
valueGlobalOp.setPrivate();
// Stash the globals for initialization.
Query query(queryLoc);
query.okGlobalOp = okGlobalOp;
query.valueGlobalOp = valueGlobalOp;
query.categoryAttr = anyQueryOp.getCategoryAttr();
query.keyAttr = anyQueryOp.getKeyAttr();
query.defaultValueAttr = anyQueryOp.getDefaultValueAttr();
queries.push_back(query);
// Replace all queries with loads of the global values.
for (auto queryOp : queryOps) {
OpBuilder replaceBuilder(queryOp);
auto okLoadOp =
okGlobalOp.createLoadOp(queryOp.getLoc(), replaceBuilder);
auto resultLoadOp =
valueGlobalOp.createLoadOp(queryOp.getLoc(), replaceBuilder);
queryOp.replaceAllUsesWith(ValueRange{
okLoadOp.getLoadedGlobalValue(),
resultLoadOp.getLoadedGlobalValue(),
});
queryOp.erase();
}
}
// Create an initializer for the device where we will perform all queries.
auto fusedLoc = moduleBuilder.getFusedLoc(
llvm::map_to_vector(queries, [&](auto &query) { return query.loc; }));
auto initializerOp =
moduleBuilder.create<IREE::Util::InitializerOp>(fusedLoc);
auto funcBuilder = OpBuilder::atBlockBegin(initializerOp.addEntryBlock());
Value device =
deviceOp.createLoadOp(fusedLoc, funcBuilder).getLoadedGlobalValue();
for (auto [i, queryKey] : llvm::enumerate(deviceQueries.queryKeys)) {
auto &query = queries[i];
auto queryOp = funcBuilder.create<IREE::HAL::DeviceQueryOp>(
fusedLoc, funcBuilder.getI1Type(),
query.valueGlobalOp.getGlobalType(), device, query.categoryAttr,
query.keyAttr, query.defaultValueAttr);
query.okGlobalOp.createStoreOp(fusedLoc, queryOp.getOk(), funcBuilder);
query.valueGlobalOp.createStoreOp(fusedLoc, queryOp.getValue(),
funcBuilder);
}
funcBuilder.create<IREE::Util::ReturnOp>(fusedLoc);
}
}
};
} // namespace
} // namespace mlir::iree_compiler::IREE::HAL