[mlir] Extended Dominance analysis with a function to find the nearest common dominator of two given blocks.

The Dominance analysis currently misses a utility function to find the nearest common dominator of two given blocks. This is required for a huge variety of different control-flow analyses and transformations. This commit adds this function and moves the getNode function from DominanceInfo to DominanceInfoBase, as it also works for post dominators.

Differential Revision: https://reviews.llvm.org/D75507
This commit is contained in:
Marcel Koester 2020-03-03 10:33:16 +01:00
parent 0e6aa08381
commit 86bbbb317b
6 changed files with 410 additions and 21 deletions

View File

@ -34,12 +34,20 @@ public:
/// Recalculate the dominance info.
void recalculate(Operation *op);
/// Finds the nearest common dominator block for the two given blocks a
/// and b. If no common dominator can be found, this function will return
/// nullptr.
Block *findNearestCommonDominator(Block *a, Block *b) const;
/// Get the root dominance node of the given region.
DominanceInfoNode *getRootNode(Region *region) {
assert(dominanceInfos.count(region) != 0);
return dominanceInfos[region]->getRootNode();
}
/// Return the dominance node from the Region containing block A.
DominanceInfoNode *getNode(Block *a);
protected:
using super = DominanceInfoBase<IsPostDom>;
@ -82,9 +90,6 @@ public:
return super::properlyDominates(a, b);
}
/// Return the dominance node from the Region containing block A.
DominanceInfoNode *getNode(Block *a);
/// Update the internal DFS numbers for the dominance nodes.
void updateDFSNumbers();
};

View File

@ -13,6 +13,7 @@
#include "mlir/Analysis/Dominance.h"
#include "mlir/IR/Operation.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/Support/GenericDomTreeConstruction.h"
using namespace mlir;
@ -43,6 +44,99 @@ void DominanceInfoBase<IsPostDom>::recalculate(Operation *op) {
});
}
/// Walks up the list of containers of the given block and calls the
/// user-defined traversal function for every pair of a region and block that
/// could be found during traversal. If the user-defined function returns true
/// for a given pair, traverseAncestors will return the current block. Nullptr
/// otherwise.
template <typename FuncT>
Block *traverseAncestors(Block *block, const FuncT &func) {
// Invoke the user-defined traversal function in the beginning for the current
// block.
if (func(block))
return block;
Region *region = block->getParent();
while (region) {
Operation *ancestor = region->getParentOp();
// If we have reached to top... return.
if (!ancestor || !(block = ancestor->getBlock()))
break;
// Update the nested region using the new ancestor block.
region = block->getParent();
// Invoke the user-defined traversal function and check whether we can
// already return.
if (func(block))
return block;
}
return nullptr;
}
/// Tries to update the given block references to live in the same region by
/// exploring the relationship of both blocks with respect to their regions.
static bool tryGetBlocksInSameRegion(Block *&a, Block *&b) {
// If both block do not live in the same region, we will have to check their
// parent operations.
if (a->getParent() == b->getParent())
return true;
// Iterate over all ancestors of a and insert them into the map. This allows
// for efficient lookups to find a commonly shared region.
llvm::SmallDenseMap<Region *, Block *, 4> ancestors;
traverseAncestors(a, [&](Block *block) {
ancestors[block->getParent()] = block;
return false;
});
// Try to find a common ancestor starting with regionB.
b = traverseAncestors(
b, [&](Block *block) { return ancestors.count(block->getParent()) > 0; });
// If there is no match, we will not be able to find a common dominator since
// both regions do not share a common parent region.
if (!b)
return false;
// We have found a common parent region. Update block a to refer to this
// region.
auto it = ancestors.find(b->getParent());
assert(it != ancestors.end());
a = it->second;
return true;
}
template <bool IsPostDom>
Block *
DominanceInfoBase<IsPostDom>::findNearestCommonDominator(Block *a,
Block *b) const {
// If either a or b are null, then conservatively return nullptr.
if (!a || !b)
return nullptr;
// Try to find blocks that are in the same region.
if (!tryGetBlocksInSameRegion(a, b))
return nullptr;
// Get and verify dominance information of the common parent region.
Region *parentRegion = a->getParent();
auto infoAIt = dominanceInfos.find(parentRegion);
if (infoAIt == dominanceInfos.end())
return nullptr;
// Since the blocks live in the same region, we can rely on already
// existing dominance functionality.
return infoAIt->second->findNearestCommonDominator(a, b);
}
template <bool IsPostDom>
DominanceInfoNode *DominanceInfoBase<IsPostDom>::getNode(Block *a) {
auto *region = a->getParent();
assert(dominanceInfos.count(region) != 0);
return dominanceInfos[region]->getNode(a);
}
/// Return true if the specified block A properly dominates block B.
template <bool IsPostDom>
bool DominanceInfoBase<IsPostDom>::properlyDominates(Block *a, Block *b) {
@ -57,21 +151,17 @@ bool DominanceInfoBase<IsPostDom>::properlyDominates(Block *a, Block *b) {
// If both blocks are not in the same region, 'a' properly dominates 'b' if
// 'b' is defined in an operation region that (recursively) ends up being
// dominated by 'a'. Walk up the list of containers enclosing B.
auto *regionA = a->getParent(), *regionB = b->getParent();
if (regionA != regionB) {
Operation *bAncestor;
do {
bAncestor = regionB->getParentOp();
// If 'bAncestor' is the top level region, then 'a' is a block that post
// dominates 'b'.
if (!bAncestor || !bAncestor->getBlock())
return IsPostDom;
auto *regionA = a->getParent();
if (regionA != b->getParent()) {
b = traverseAncestors(
b, [&](Block *block) { return block->getParent() == regionA; });
regionB = bAncestor->getBlock()->getParent();
} while (regionA != regionB);
// If we could not find a valid block b then it is either a not a dominator
// or a post dominator.
if (!b)
return IsPostDom;
// Check to see if the ancestor of 'b' is the same block as 'a'.
b = bAncestor->getBlock();
if (a == b)
return true;
}
@ -132,12 +222,6 @@ bool DominanceInfo::properlyDominates(Value a, Operation *b) {
return dominates(a.cast<BlockArgument>().getOwner(), b->getBlock());
}
DominanceInfoNode *DominanceInfo::getNode(Block *a) {
auto *region = a->getParent();
assert(dominanceInfos.count(region) != 0);
return dominanceInfos[region]->getNode(a);
}
void DominanceInfo::updateDFSNumbers() {
for (auto &iter : dominanceInfos)
iter.second->updateDFSNumbers();

View File

@ -0,0 +1,207 @@
// RUN: mlir-opt %s -test-print-dominance -split-input-file 2>&1 | FileCheck %s --dump-input-on-failure
// CHECK-LABEL: Testing : func_condBranch
func @func_condBranch(%cond : i1) {
cond_br %cond, ^bb1, ^bb2
^bb1:
br ^exit
^bb2:
br ^exit
^exit:
return
}
// CHECK-LABEL: --- DominanceInfo ---
// CHECK-NEXT: Nearest(0, 0) = 0
// CHECK-NEXT: Nearest(0, 1) = 0
// CHECK-NEXT: Nearest(0, 2) = 0
// CHECK-NEXT: Nearest(0, 3) = 0
// CHECK: Nearest(1, 0) = 0
// CHECK-NEXT: Nearest(1, 1) = 1
// CHECK-NEXT: Nearest(1, 2) = 0
// CHECK-NEXT: Nearest(1, 3) = 0
// CHECK: Nearest(2, 0) = 0
// CHECK-NEXT: Nearest(2, 1) = 0
// CHECK-NEXT: Nearest(2, 2) = 2
// CHECK-NEXT: Nearest(2, 3) = 0
// CHECK: Nearest(3, 0) = 0
// CHECK-NEXT: Nearest(3, 1) = 0
// CHECK-NEXT: Nearest(3, 2) = 0
// CHECK-NEXT: Nearest(3, 3) = 3
// CHECK-LABEL: --- PostDominanceInfo ---
// CHECK-NEXT: Nearest(0, 0) = 0
// CHECK-NEXT: Nearest(0, 1) = 3
// CHECK-NEXT: Nearest(0, 2) = 3
// CHECK-NEXT: Nearest(0, 3) = 3
// CHECK: Nearest(1, 0) = 3
// CHECK-NEXT: Nearest(1, 1) = 1
// CHECK-NEXT: Nearest(1, 2) = 3
// CHECK-NEXT: Nearest(1, 3) = 3
// CHECK: Nearest(2, 0) = 3
// CHECK-NEXT: Nearest(2, 1) = 3
// CHECK-NEXT: Nearest(2, 2) = 2
// CHECK-NEXT: Nearest(2, 3) = 3
// CHECK: Nearest(3, 0) = 3
// CHECK-NEXT: Nearest(3, 1) = 3
// CHECK-NEXT: Nearest(3, 2) = 3
// CHECK-NEXT: Nearest(3, 3) = 3
// -----
// CHECK-LABEL: Testing : func_loop
func @func_loop(%arg0 : i32, %arg1 : i32) {
br ^loopHeader(%arg0 : i32)
^loopHeader(%counter : i32):
%lessThan = cmpi "slt", %counter, %arg1 : i32
cond_br %lessThan, ^loopBody, ^exit
^loopBody:
%const0 = constant 1 : i32
%inc = addi %counter, %const0 : i32
br ^loopHeader(%inc : i32)
^exit:
return
}
// CHECK-LABEL: --- DominanceInfo ---
// CHECK: Nearest(1, 0) = 0
// CHECK-NEXT: Nearest(1, 1) = 1
// CHECK-NEXT: Nearest(1, 2) = 1
// CHECK-NEXT: Nearest(1, 3) = 1
// CHECK: Nearest(2, 0) = 0
// CHECK-NEXT: Nearest(2, 1) = 1
// CHECK-NEXT: Nearest(2, 2) = 2
// CHECK-NEXT: Nearest(2, 3) = 1
// CHECK: Nearest(3, 0) = 0
// CHECK-NEXT: Nearest(3, 1) = 1
// CHECK-NEXT: Nearest(3, 2) = 1
// CHECK-NEXT: Nearest(3, 3) = 3
// CHECK-LABEL: --- PostDominanceInfo ---
// CHECK: Nearest(1, 0) = 1
// CHECK-NEXT: Nearest(1, 1) = 1
// CHECK-NEXT: Nearest(1, 2) = 1
// CHECK-NEXT: Nearest(1, 3) = 3
// CHECK: Nearest(2, 0) = 1
// CHECK-NEXT: Nearest(2, 1) = 1
// CHECK-NEXT: Nearest(2, 2) = 2
// CHECK-NEXT: Nearest(2, 3) = 3
// CHECK: Nearest(3, 0) = 3
// CHECK-NEXT: Nearest(3, 1) = 3
// CHECK-NEXT: Nearest(3, 2) = 3
// CHECK-NEXT: Nearest(3, 3) = 3
// -----
// CHECK-LABEL: Testing : nested_region
func @nested_region(%arg0 : index, %arg1 : index, %arg2 : index) {
loop.for %arg3 = %arg0 to %arg1 step %arg2 { }
return
}
// CHECK-LABEL: --- DominanceInfo ---
// CHECK-NEXT: Nearest(0, 0) = 0
// CHECK-NEXT: Nearest(0, 1) = 1
// CHECK: Nearest(1, 0) = 1
// CHECK-NEXT: Nearest(1, 1) = 1
// CHECK-LABEL: --- PostDominanceInfo ---
// CHECK-NEXT: Nearest(0, 0) = 0
// CHECK-NEXT: Nearest(0, 1) = 1
// CHECK: Nearest(1, 0) = 1
// CHECK-NEXT: Nearest(1, 1) = 1
// -----
// CHECK-LABEL: Testing : nested_region2
func @nested_region2(%arg0 : index, %arg1 : index, %arg2 : index) {
loop.for %arg3 = %arg0 to %arg1 step %arg2 {
loop.for %arg4 = %arg0 to %arg1 step %arg2 {
loop.for %arg5 = %arg0 to %arg1 step %arg2 { }
}
}
return
}
// CHECK-LABEL: --- DominanceInfo ---
// CHECK: Nearest(1, 0) = 1
// CHECK-NEXT: Nearest(1, 1) = 1
// CHECK-NEXT: Nearest(1, 2) = 2
// CHECK-NEXT: Nearest(1, 3) = 3
// CHECK: Nearest(2, 0) = 2
// CHECK-NEXT: Nearest(2, 1) = 2
// CHECK-NEXT: Nearest(2, 2) = 2
// CHECK-NEXT: Nearest(2, 3) = 3
// CHECK: Nearest(3, 0) = 3
// CHECK-NEXT: Nearest(3, 1) = 3
// CHECK-NEXT: Nearest(3, 2) = 3
// CHECK-NEXT: Nearest(3, 3) = 3
// CHECK-LABEL: --- PostDominanceInfo ---
// CHECK-NEXT: Nearest(0, 0) = 0
// CHECK-NEXT: Nearest(0, 1) = 1
// CHECK-NEXT: Nearest(0, 2) = 2
// CHECK-NEXT: Nearest(0, 3) = 3
// CHECK: Nearest(1, 0) = 1
// CHECK-NEXT: Nearest(1, 1) = 1
// CHECK-NEXT: Nearest(1, 2) = 2
// CHECK-NEXT: Nearest(1, 3) = 3
// CHECK: Nearest(2, 0) = 2
// CHECK-NEXT: Nearest(2, 1) = 2
// CHECK-NEXT: Nearest(2, 2) = 2
// CHECK-NEXT: Nearest(2, 3) = 3
// -----
// CHECK-LABEL: Testing : func_loop_nested_region
func @func_loop_nested_region(
%arg0 : i32,
%arg1 : i32,
%arg2 : index,
%arg3 : index,
%arg4 : index) {
br ^loopHeader(%arg0 : i32)
^loopHeader(%counter : i32):
%lessThan = cmpi "slt", %counter, %arg1 : i32
cond_br %lessThan, ^loopBody, ^exit
^loopBody:
%const0 = constant 1 : i32
%inc = addi %counter, %const0 : i32
loop.for %arg5 = %arg2 to %arg3 step %arg4 {
loop.for %arg6 = %arg2 to %arg3 step %arg4 { }
}
br ^loopHeader(%inc : i32)
^exit:
return
}
// CHECK-LABEL: --- DominanceInfo ---
// CHECK: Nearest(2, 0) = 0
// CHECK-NEXT: Nearest(2, 1) = 1
// CHECK-NEXT: Nearest(2, 2) = 2
// CHECK-NEXT: Nearest(2, 3) = 2
// CHECK-NEXT: Nearest(2, 4) = 2
// CHECK-NEXT: Nearest(2, 5) = 1
// CHECK: Nearest(3, 0) = 0
// CHECK-NEXT: Nearest(3, 1) = 1
// CHECK-NEXT: Nearest(3, 2) = 2
// CHECK-NEXT: Nearest(3, 3) = 3
// CHECK-NEXT: Nearest(3, 4) = 4
// CHECK-NEXT: Nearest(3, 5) = 1
// CHECK: Nearest(4, 0) = 0
// CHECK-NEXT: Nearest(4, 1) = 1
// CHECK-NEXT: Nearest(4, 2) = 2
// CHECK-NEXT: Nearest(4, 3) = 4
// CHECK-NEXT: Nearest(4, 4) = 4
// CHECK-NEXT: Nearest(4, 5) = 1
// CHECK-LABEL: --- PostDominanceInfo ---
// CHECK: Nearest(2, 0) = 1
// CHECK-NEXT: Nearest(2, 1) = 1
// CHECK-NEXT: Nearest(2, 2) = 2
// CHECK-NEXT: Nearest(2, 3) = 2
// CHECK-NEXT: Nearest(2, 4) = 2
// CHECK-NEXT: Nearest(2, 5) = 5
// CHECK: Nearest(3, 0) = 1
// CHECK-NEXT: Nearest(3, 1) = 1
// CHECK-NEXT: Nearest(3, 2) = 2
// CHECK-NEXT: Nearest(3, 3) = 3
// CHECK-NEXT: Nearest(3, 4) = 4
// CHECK-NEXT: Nearest(3, 5) = 5
// CHECK: Nearest(4, 0) = 1
// CHECK-NEXT: Nearest(4, 1) = 1
// CHECK-NEXT: Nearest(4, 2) = 2
// CHECK-NEXT: Nearest(4, 3) = 4
// CHECK-NEXT: Nearest(4, 4) = 4
// CHECK-NEXT: Nearest(4, 5) = 5

View File

@ -3,6 +3,7 @@ add_llvm_library(MLIRTestTransforms
TestCallGraph.cpp
TestConstantFold.cpp
TestConvertGPUKernelToCubin.cpp
TestDominance.cpp
TestLoopFusion.cpp
TestGpuMemoryPromotion.cpp
TestGpuParallelLoopMapping.cpp

View File

@ -0,0 +1,90 @@
//===- TestDominance.cpp - Test dominance construction and information
//-------===//
//
// Part of the LLVM Project, 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
//
//===----------------------------------------------------------------------===//
//
// This file contains test passes for constructing and resolving dominance
// information.
//
//===----------------------------------------------------------------------===//
#include "mlir/Analysis/Dominance.h"
#include "mlir/Pass/Pass.h"
using namespace mlir;
namespace {
/// Helper class to print dominance information.
class DominanceTest {
public:
/// Constructs a new test instance using the given operation.
DominanceTest(Operation *operation) : operation(operation) {
// Create unique ids for each block.
operation->walk([&](Operation *nested) {
if (blockIds.count(nested->getBlock()) > 0)
return;
blockIds.insert({nested->getBlock(), blockIds.size()});
});
}
/// Prints dominance information of all blocks.
template <typename DominanceT>
void printDominance(DominanceT &dominanceInfo) {
DenseSet<Block *> parentVisited;
operation->walk([&](Operation *op) {
Block *block = op->getBlock();
if (!parentVisited.insert(block).second)
return;
DenseSet<Block *> visited;
operation->walk([&](Operation *nested) {
Block *nestedBlock = nested->getBlock();
if (!visited.insert(nestedBlock).second)
return;
llvm::errs() << "Nearest(" << blockIds[block] << ", "
<< blockIds[nestedBlock] << ") = ";
Block *dom =
dominanceInfo.findNearestCommonDominator(block, nestedBlock);
if (dom)
llvm::errs() << blockIds[dom];
else
llvm::errs() << "<no dom>";
llvm::errs() << "\n";
});
});
}
private:
Operation *operation;
DenseMap<Block *, size_t> blockIds;
};
struct TestDominancePass : public FunctionPass<TestDominancePass> {
void runOnFunction() override {
llvm::errs() << "Testing : " << getFunction().getName() << "\n";
DominanceTest dominanceTest(getFunction());
// Print dominance information.
llvm::errs() << "--- DominanceInfo ---\n";
dominanceTest.printDominance(getAnalysis<DominanceInfo>());
llvm::errs() << "--- PostDominanceInfo ---\n";
dominanceTest.printDominance(getAnalysis<PostDominanceInfo>());
}
};
} // end anonymous namespace
namespace mlir {
void registerTestDominancePass() {
PassRegistration<TestDominancePass>(
"test-print-dominance",
"Print the dominance information for multiple regions.");
}
} // namespace mlir

View File

@ -42,6 +42,7 @@ void registerTestAllReduceLoweringPass();
void registerTestCallGraphPass();
void registerTestConstantFold();
void registerTestConvertGPUKernelToCubinPass();
void registerTestDominancePass();
void registerTestFunc();
void registerTestGpuMemoryPromotionPass();
void registerTestLinalgTransforms();
@ -100,6 +101,7 @@ void registerTestPasses() {
#if MLIR_CUDA_CONVERSIONS_ENABLED
registerTestConvertGPUKernelToCubinPass();
#endif
registerTestDominancePass();
registerTestFunc();
registerTestGpuMemoryPromotionPass();
registerTestLinalgTransforms();