blob: 2a076d982f5e23fc818f4bce9b61ac992bc06cd1 [file]
// Copyright 2021 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 <algorithm>
#include <iterator>
#include "iree/compiler/Dialect/Util/Analysis/GlobalTable.h"
#include "iree/compiler/Dialect/Util/IR/UtilDialect.h"
#include "iree/compiler/Dialect/Util/IR/UtilOps.h"
#include "iree/compiler/Dialect/Util/IR/UtilTraits.h"
#include "iree/compiler/Dialect/Util/Transforms/Passes.h"
#include "llvm/ADT/BitVector.h"
#include "llvm/ADT/EquivalenceClasses.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/Debug.h"
#include "mlir/IR/AsmState.h"
#include "mlir/IR/Matchers.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Pass/PassRegistry.h"
#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
#define DEBUG_TYPE "iree-util-fuse-globals"
namespace mlir::iree_compiler::IREE::Util {
#define GEN_PASS_DEF_FUSEGLOBALSPASS
#include "iree/compiler/Dialect/Util/Transforms/Passes.h.inc"
namespace {
static llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
llvm::BitVector &bits) {
for (unsigned i = 0; i < bits.size(); ++i) {
os << (bits.test(i) ? "1" : "0");
}
return os;
}
// Fuses globals that are always set to the same value into one.
//
// Example:
// util.global mutable @a : i32
// util.global mutable @b : i32
// builtin.func @foo(%arg0: i32) {
// util.global.store %arg0, @a : i32
// util.global.store %arg0, @b : i32
// ->
// util.global mutable @fused : i32
// builtin.func @foo(%arg0: i32) {
// util.global.store %arg0, @fused : i32
class FuseGlobalsPass : public impl::FuseGlobalsPassBase<FuseGlobalsPass> {
public:
void runOnOperation() override {
auto moduleOp = getOperation();
GlobalTable globalTable(moduleOp);
globalTable.rebuild();
// Build a map of global symbol to a bitvector indicating which globals are
// stored with the same values in all instances.
// This is done by walking the values stored into globals and ANDing a
// bitmask of the other globals stored with the same value.
//
// Note that we are only looking for stores within the same block - we
// expect other canonicalizations to have moved stores into the same block
// that are guaranteed to be on the same execution path.
DenseMap<StringRef, llvm::BitVector> correlationMap;
llvm::BitVector tempBits(globalTable.size());
for (auto callableOp : moduleOp.getOps<CallableOpInterface>()) {
std::unique_ptr<AsmState> asmState;
LLVM_DEBUG({
asmState = std::make_unique<AsmState>(callableOp);
llvm::dbgs() << "FuseGlobals: analyzing ";
callableOp.print(llvm::dbgs(), *asmState);
llvm::dbgs() << ":\n";
});
auto *region = callableOp.getCallableRegion();
if (!region)
continue;
for (auto &block : *region) {
DenseMap<Value, SmallVector<IREE::Util::GlobalStoreOpInterface>>
valueStores;
for (auto storeOp :
block.getOps<IREE::Util::GlobalStoreOpInterface>()) {
auto &global = globalTable.lookup(storeOp.getGlobalName());
LLVM_DEBUG({
llvm::dbgs() << " - store #" << global.ordinal << ": ";
storeOp.print(llvm::dbgs(), *asmState);
llvm::dbgs() << "; candidate=" << global.isCandidate() << "\n";
});
if (!global.isCandidate())
continue;
valueStores[storeOp.getStoredGlobalValue()].push_back(storeOp);
}
for (auto valueStore : valueStores) {
LLVM_DEBUG({
llvm::dbgs() << "= storing value ";
valueStore.first.printAsOperand(llvm::dbgs(), *asmState);
llvm::dbgs() << ":\n";
for (auto storeOp : valueStore.second) {
llvm::dbgs() << " => @" << storeOp.getGlobalName() << "\n";
}
});
tempBits.reset();
for (auto storeOp : valueStore.second) {
auto &global = globalTable.lookup(storeOp.getGlobalName());
tempBits.set(global.ordinal);
}
for (auto storeOp : valueStore.second) {
auto entry = correlationMap.find(storeOp.getGlobalName());
if (entry == correlationMap.end()) {
correlationMap.insert(
std::make_pair(storeOp.getGlobalName(), tempBits));
} else {
entry->second &= tempBits;
}
}
}
}
}
// Resolve which globals are always set to the same value.
// This ensures that if @a is set to @b that @b is also set to @a.
// TODO(benvanik): find a better data structure that avoids the need for
// this cleanup step. We should be able to do this during construction.
for (auto it : correlationMap) {
auto globalName = it.first;
auto &correlationBits = it.second;
auto &global = globalTable.lookup(globalName);
llvm::BitVector tempBits = correlationBits;
for (auto ordinal : correlationBits.set_bits()) {
auto otherGlobalName = globalTable.lookupByOrdinal(ordinal);
if (otherGlobalName == globalName) {
continue;
}
auto &otherBits = correlationMap[otherGlobalName];
if (!otherBits.test(global.ordinal)) {
LLVM_DEBUG(llvm::dbgs() << "Fixup: " << globalName
<< " uncorrelated with " << otherGlobalName
<< ", masking off " << otherBits << "\n");
tempBits.reset(otherBits);
} else {
tempBits &= otherBits;
}
}
correlationMap[globalName] = tempBits;
}
LLVM_DEBUG({
llvm::dbgs() << "FuseGlobals correlation maps:\n";
for (auto it : correlationMap) {
auto globalName = it.first;
auto &correlationBits = it.second;
auto &global = globalTable.lookup(globalName);
llvm::dbgs() << "= #" << global.ordinal << " " << global.getName()
<< " = " << correlationBits << ":\n";
for (auto ordinal : correlationBits.set_bits()) {
llvm::dbgs() << " => " << globalTable.lookupByOrdinal(ordinal)
<< "\n";
}
}
});
// Build equivalence classes for each global, giving us nice clustered sets.
// We could probably fold this with the step above but my head hurts.
llvm::EquivalenceClasses<StringRef> ec;
for (auto it : correlationMap) {
auto globalName = it.first;
auto &correlationBits = it.second;
auto &global = globalTable.lookup(globalName);
for (auto ordinal : correlationBits.set_bits()) {
ec.unionSets(global.getName(), globalTable.lookupByOrdinal(ordinal));
}
}
// Build the sets of fusable globals. We use the equivalence classes we
// built above to know which globals _should_ fuse, and this check lets us
// filter out globals that _cannot_ fuse; such as when their initializers
// differ.
SmallVector<SmallVector<Global *>> fusableSets;
for (auto it = ec.begin(), end = ec.end(); it != end; ++it) {
if (!it->isLeader()) {
continue; // Ignore non-leader sets.
}
if (++ec.member_begin(it) == ec.member_end()) {
continue; // size 1
}
DenseMap<Attribute, SmallVector<Global *>> initialValueMap;
for (auto mi = ec.member_begin(it); mi != ec.member_end(); ++mi) {
Global &global = globalTable.lookup(*mi);
initialValueMap[global.op.getGlobalInitialValue()].push_back(&global);
}
for (auto it : initialValueMap) {
fusableSets.push_back(std::move(it.second));
}
}
// For each foldable set combine into a single global and update all uses.
SymbolTable symbolTable(moduleOp);
SmallVector<StringRef> deadGlobalNames;
for (auto &fusableSet : fusableSets) {
auto *baseGlobal = fusableSet.front();
LLVM_DEBUG(llvm::dbgs()
<< "Fusing " << fusableSet.size() << " globals into "
<< baseGlobal->getName() << "\n");
// Build fused location from all of the globals.
SmallVector<Location> locs;
for (auto *global : fusableSet) {
locs.push_back(global->op.getLoc());
if (global->ordinal < baseGlobal->ordinal) {
baseGlobal = global;
}
}
auto fusedLoc = FusedLoc::get(moduleOp.getContext(), locs);
// Update base global location.
IREE::Util::GlobalOpInterface baseGlobalOp = baseGlobal->op;
baseGlobalOp->setLoc(fusedLoc);
// Replace all globals to point at the new one.
for (auto *global : fusableSet) {
if (global->op == baseGlobalOp) {
continue;
}
globalTable.renameGlobalUses(*global, *baseGlobal);
deadGlobalNames.push_back(global->getName());
}
}
for (auto globalName : deadGlobalNames) {
globalTable.eraseGlobal(globalName);
}
}
};
} // namespace
} // namespace mlir::iree_compiler::IREE::Util