mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2024-11-22 10:42:39 +01:00
b98fe759b3
Summary: This patch separates two semantics of `applyUpdates`: 1. User provides an accurate CFG diff and the dominator tree is updated according to the difference of `the number of edge insertions` and `the number of edge deletions` to infer the status of an edge before and after the update. 2. User provides a sequence of hints. Updates mentioned in this sequence might never happened and even duplicated. Logic changes: Previously, removing invalid updates is considered a side-effect of deduplication and is not guaranteed to be reliable. To handle the second semantic, `applyUpdates` does validity checking before deduplication, which can cause updates that have already been applied to be submitted again. Then, different calls to `applyUpdates` might cause unintended consequences, for example, ``` DTU(Lazy) and Edge A->B exists. 1. DTU.applyUpdates({{Delete, A, B}, {Insert, A, B}}) // User expects these 2 updates result in a no-op, but {Insert, A, B} is queued 2. Remove A->B 3. DTU.applyUpdates({{Delete, A, B}}) // DTU cancels this update with {Insert, A, B} mentioned above together (Unintended) ``` But by restricting the precondition that updates of an edge need to be strictly ordered as how CFG changes were made, we can infer the initial status of this edge to resolve this issue. Interface changes: The second semantic of `applyUpdates` is separated to `applyUpdatesPermissive`. These changes enable DTU(Lazy) to use the first semantic if needed, which is quite useful in `transforms/utils`. Reviewers: kuhar, brzycki, dmgreen, grosser Reviewed By: brzycki Subscribers: hiraditya, llvm-commits Tags: #llvm Differential Revision: https://reviews.llvm.org/D58170 llvm-svn: 354669
795 lines
29 KiB
C++
795 lines
29 KiB
C++
//===- DomTreeUpdaterTest.cpp - DomTreeUpdater unit tests -----------------===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "llvm/Analysis/DomTreeUpdater.h"
|
|
#include "llvm/Analysis/PostDominators.h"
|
|
#include "llvm/AsmParser/Parser.h"
|
|
#include "llvm/IR/Constants.h"
|
|
#include "llvm/IR/Dominators.h"
|
|
#include "llvm/IR/Instructions.h"
|
|
#include "llvm/IR/LLVMContext.h"
|
|
#include "llvm/IR/Module.h"
|
|
#include "llvm/Support/SourceMgr.h"
|
|
#include "gtest/gtest.h"
|
|
#include <algorithm>
|
|
|
|
using namespace llvm;
|
|
|
|
static std::unique_ptr<Module> makeLLVMModule(LLVMContext &Context,
|
|
StringRef ModuleStr) {
|
|
SMDiagnostic Err;
|
|
std::unique_ptr<Module> M = parseAssemblyString(ModuleStr, Err, Context);
|
|
assert(M && "Bad LLVM IR?");
|
|
return M;
|
|
}
|
|
|
|
TEST(DomTreeUpdater, EagerUpdateBasicOperations) {
|
|
StringRef FuncName = "f";
|
|
StringRef ModuleString = R"(
|
|
define i32 @f(i32 %i, i32 *%p) {
|
|
bb0:
|
|
store i32 %i, i32 *%p
|
|
switch i32 %i, label %bb1 [
|
|
i32 1, label %bb2
|
|
i32 2, label %bb3
|
|
]
|
|
bb1:
|
|
ret i32 1
|
|
bb2:
|
|
ret i32 2
|
|
bb3:
|
|
ret i32 3
|
|
})";
|
|
// Make the module.
|
|
LLVMContext Context;
|
|
std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString);
|
|
Function *F = M->getFunction(FuncName);
|
|
|
|
// Make the DomTreeUpdater.
|
|
DominatorTree DT(*F);
|
|
PostDominatorTree PDT(*F);
|
|
DomTreeUpdater DTU(DT, PDT, DomTreeUpdater::UpdateStrategy::Eager);
|
|
|
|
ASSERT_TRUE(DTU.hasDomTree());
|
|
ASSERT_TRUE(DTU.hasPostDomTree());
|
|
ASSERT_TRUE(DTU.isEager());
|
|
ASSERT_FALSE(DTU.isLazy());
|
|
ASSERT_TRUE(DTU.getDomTree().verify());
|
|
ASSERT_TRUE(DTU.getPostDomTree().verify());
|
|
ASSERT_FALSE(DTU.hasPendingUpdates());
|
|
|
|
Function::iterator FI = F->begin();
|
|
BasicBlock *BB0 = &*FI++;
|
|
BasicBlock *BB1 = &*FI++;
|
|
BasicBlock *BB2 = &*FI++;
|
|
BasicBlock *BB3 = &*FI++;
|
|
SwitchInst *SI = dyn_cast<SwitchInst>(BB0->getTerminator());
|
|
ASSERT_NE(SI, nullptr) << "Couldn't get SwitchInst.";
|
|
|
|
DTU.applyUpdatesPermissive(
|
|
{{DominatorTree::Insert, BB0, BB0}, {DominatorTree::Delete, BB0, BB0}});
|
|
ASSERT_FALSE(DTU.hasPendingUpdates());
|
|
|
|
// Delete edge bb0 -> bb3 and push the update twice to verify duplicate
|
|
// entries are discarded.
|
|
std::vector<DominatorTree::UpdateType> Updates;
|
|
Updates.reserve(4);
|
|
Updates.push_back({DominatorTree::Delete, BB0, BB3});
|
|
Updates.push_back({DominatorTree::Delete, BB0, BB3});
|
|
|
|
// Invalid Insert: no edge bb1 -> bb2 after change to bb0.
|
|
Updates.push_back({DominatorTree::Insert, BB1, BB2});
|
|
// Invalid Delete: edge exists bb0 -> bb1 after change to bb0.
|
|
Updates.push_back({DominatorTree::Delete, BB0, BB1});
|
|
|
|
// CFG Change: remove edge bb0 -> bb3.
|
|
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 3u);
|
|
BB3->removePredecessor(BB0);
|
|
for (auto i = SI->case_begin(), e = SI->case_end(); i != e; ++i) {
|
|
if (i->getCaseSuccessor() == BB3) {
|
|
SI->removeCase(i);
|
|
break;
|
|
}
|
|
}
|
|
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 2u);
|
|
// Deletion of a BasicBlock is an immediate event. We remove all uses to the
|
|
// contained Instructions and change the Terminator to "unreachable" when
|
|
// queued for deletion.
|
|
ASSERT_FALSE(isa<UnreachableInst>(BB3->getTerminator()));
|
|
EXPECT_FALSE(DTU.isBBPendingDeletion(BB3));
|
|
DTU.applyUpdatesPermissive(Updates);
|
|
ASSERT_FALSE(DTU.hasPendingUpdates());
|
|
|
|
// Invalid Insert: no edge bb1 -> bb2 after change to bb0.
|
|
// Invalid Delete: edge exists bb0 -> bb1 after change to bb0.
|
|
DTU.applyUpdatesPermissive(
|
|
{{DominatorTree::Insert, BB1, BB2}, {DominatorTree::Delete, BB0, BB1}});
|
|
|
|
// DTU working with Eager UpdateStrategy does not need to flush.
|
|
ASSERT_TRUE(DT.verify());
|
|
ASSERT_TRUE(PDT.verify());
|
|
|
|
// Test callback utils.
|
|
ASSERT_EQ(BB3->getParent(), F);
|
|
DTU.callbackDeleteBB(BB3,
|
|
[&F](BasicBlock *BB) { ASSERT_NE(BB->getParent(), F); });
|
|
|
|
ASSERT_TRUE(DT.verify());
|
|
ASSERT_TRUE(PDT.verify());
|
|
ASSERT_FALSE(DTU.hasPendingUpdates());
|
|
|
|
// Unnecessary flush() test
|
|
DTU.flush();
|
|
EXPECT_TRUE(DT.verify());
|
|
EXPECT_TRUE(PDT.verify());
|
|
|
|
// Remove all case branch to BB2 to test Eager recalculation.
|
|
// Code section from llvm::ConstantFoldTerminator
|
|
for (auto i = SI->case_begin(), e = SI->case_end(); i != e;) {
|
|
if (i->getCaseSuccessor() == BB2) {
|
|
// Remove this entry.
|
|
BB2->removePredecessor(BB0);
|
|
i = SI->removeCase(i);
|
|
e = SI->case_end();
|
|
} else
|
|
++i;
|
|
}
|
|
ASSERT_FALSE(DT.verify());
|
|
ASSERT_FALSE(PDT.verify());
|
|
DTU.recalculate(*F);
|
|
ASSERT_TRUE(DT.verify());
|
|
ASSERT_TRUE(PDT.verify());
|
|
}
|
|
|
|
TEST(DomTreeUpdater, EagerUpdateReplaceEntryBB) {
|
|
StringRef FuncName = "f";
|
|
StringRef ModuleString = R"(
|
|
define i32 @f() {
|
|
bb0:
|
|
br label %bb1
|
|
bb1:
|
|
ret i32 1
|
|
}
|
|
)";
|
|
// Make the module.
|
|
LLVMContext Context;
|
|
std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString);
|
|
Function *F = M->getFunction(FuncName);
|
|
|
|
// Make the DTU.
|
|
DominatorTree DT(*F);
|
|
PostDominatorTree PDT(*F);
|
|
DomTreeUpdater DTU(DT, PDT, DomTreeUpdater::UpdateStrategy::Eager);
|
|
ASSERT_TRUE(DTU.hasDomTree());
|
|
ASSERT_TRUE(DTU.hasPostDomTree());
|
|
ASSERT_TRUE(DTU.isEager());
|
|
ASSERT_FALSE(DTU.isLazy());
|
|
ASSERT_TRUE(DT.verify());
|
|
ASSERT_TRUE(PDT.verify());
|
|
|
|
Function::iterator FI = F->begin();
|
|
BasicBlock *BB0 = &*FI++;
|
|
BasicBlock *BB1 = &*FI++;
|
|
|
|
// Add a block as the new function entry BB. We also link it to BB0.
|
|
BasicBlock *NewEntry =
|
|
BasicBlock::Create(F->getContext(), "new_entry", F, BB0);
|
|
BranchInst::Create(BB0, NewEntry);
|
|
EXPECT_EQ(F->begin()->getName(), NewEntry->getName());
|
|
EXPECT_TRUE(&F->getEntryBlock() == NewEntry);
|
|
|
|
DTU.applyUpdates({{DominatorTree::Insert, NewEntry, BB0}});
|
|
|
|
// Changing the Entry BB requires a full recalculation of DomTree.
|
|
DTU.recalculate(*F);
|
|
ASSERT_TRUE(DT.verify());
|
|
ASSERT_TRUE(PDT.verify());
|
|
|
|
// CFG Change: remove new_edge -> bb0 and redirect to new_edge -> bb1.
|
|
EXPECT_EQ(NewEntry->getTerminator()->getNumSuccessors(), 1u);
|
|
NewEntry->getTerminator()->eraseFromParent();
|
|
BranchInst::Create(BB1, NewEntry);
|
|
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 1u);
|
|
|
|
// Update the DTU. At this point bb0 now has no predecessors but is still a
|
|
// Child of F.
|
|
DTU.applyUpdates({{DominatorTree::Delete, NewEntry, BB0},
|
|
{DominatorTree::Insert, NewEntry, BB1}});
|
|
ASSERT_TRUE(DT.verify());
|
|
ASSERT_TRUE(PDT.verify());
|
|
|
|
// Now remove bb0 from F.
|
|
ASSERT_FALSE(isa<UnreachableInst>(BB0->getTerminator()));
|
|
EXPECT_FALSE(DTU.isBBPendingDeletion(BB0));
|
|
DTU.deleteBB(BB0);
|
|
ASSERT_TRUE(DT.verify());
|
|
ASSERT_TRUE(PDT.verify());
|
|
}
|
|
|
|
TEST(DomTreeUpdater, LazyUpdateDTBasicOperations) {
|
|
StringRef FuncName = "f";
|
|
StringRef ModuleString = R"(
|
|
define i32 @f(i32 %i, i32 *%p) {
|
|
bb0:
|
|
store i32 %i, i32 *%p
|
|
switch i32 %i, label %bb1 [
|
|
i32 0, label %bb2
|
|
i32 1, label %bb2
|
|
i32 2, label %bb3
|
|
]
|
|
bb1:
|
|
ret i32 1
|
|
bb2:
|
|
ret i32 2
|
|
bb3:
|
|
ret i32 3
|
|
}
|
|
)";
|
|
// Make the module.
|
|
LLVMContext Context;
|
|
std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString);
|
|
Function *F = M->getFunction(FuncName);
|
|
|
|
// Make the DTU.
|
|
DominatorTree DT(*F);
|
|
PostDominatorTree *PDT = nullptr;
|
|
DomTreeUpdater DTU(&DT, PDT, DomTreeUpdater::UpdateStrategy::Lazy);
|
|
ASSERT_TRUE(DTU.hasDomTree());
|
|
ASSERT_FALSE(DTU.hasPostDomTree());
|
|
ASSERT_FALSE(DTU.isEager());
|
|
ASSERT_TRUE(DTU.isLazy());
|
|
ASSERT_TRUE(DTU.getDomTree().verify());
|
|
|
|
Function::iterator FI = F->begin();
|
|
BasicBlock *BB0 = &*FI++;
|
|
BasicBlock *BB1 = &*FI++;
|
|
BasicBlock *BB2 = &*FI++;
|
|
BasicBlock *BB3 = &*FI++;
|
|
|
|
// Test discards of self-domination update.
|
|
DTU.applyUpdatesPermissive({{DominatorTree::Insert, BB0, BB0}});
|
|
ASSERT_FALSE(DTU.hasPendingDomTreeUpdates());
|
|
|
|
// Delete edge bb0 -> bb3 and push the update twice to verify duplicate
|
|
// entries are discarded.
|
|
std::vector<DominatorTree::UpdateType> Updates;
|
|
Updates.reserve(4);
|
|
Updates.push_back({DominatorTree::Delete, BB0, BB3});
|
|
Updates.push_back({DominatorTree::Delete, BB0, BB3});
|
|
|
|
// Invalid Insert: no edge bb1 -> bb2 after change to bb0.
|
|
Updates.push_back({DominatorTree::Insert, BB1, BB2});
|
|
// Invalid Delete: edge exists bb0 -> bb1 after change to bb0.
|
|
Updates.push_back({DominatorTree::Delete, BB0, BB1});
|
|
|
|
// CFG Change: remove edge bb0 -> bb3 and one duplicate edge bb0 -> bb2.
|
|
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 4u);
|
|
BB0->getTerminator()->eraseFromParent();
|
|
BranchInst::Create(BB1, BB2, ConstantInt::getTrue(F->getContext()), BB0);
|
|
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 2u);
|
|
|
|
// Verify. Updates to DTU must be applied *after* all changes to the CFG
|
|
// (including block deletion).
|
|
DTU.applyUpdatesPermissive(Updates);
|
|
ASSERT_TRUE(DTU.getDomTree().verify());
|
|
|
|
// Deletion of a BasicBlock is an immediate event. We remove all uses to the
|
|
// contained Instructions and change the Terminator to "unreachable" when
|
|
// queued for deletion. Its parent is still F until all the pending updates
|
|
// are applied to all trees held by the DomTreeUpdater (DomTree/PostDomTree).
|
|
// We don't defer this action because it can cause problems for other
|
|
// transforms or analysis as it's part of the actual CFG. We only defer
|
|
// updates to the DominatorTrees. This code will crash if it is placed before
|
|
// the BranchInst::Create() call above. After a deletion of a BasicBlock. Only
|
|
// an explicit flush event can trigger the flushing of deleteBBs. Because some
|
|
// passes using Lazy UpdateStrategy rely on this behavior.
|
|
|
|
ASSERT_FALSE(isa<UnreachableInst>(BB3->getTerminator()));
|
|
EXPECT_FALSE(DTU.isBBPendingDeletion(BB3));
|
|
EXPECT_FALSE(DTU.hasPendingDeletedBB());
|
|
DTU.deleteBB(BB3);
|
|
EXPECT_TRUE(DTU.isBBPendingDeletion(BB3));
|
|
EXPECT_TRUE(DTU.hasPendingDeletedBB());
|
|
ASSERT_TRUE(isa<UnreachableInst>(BB3->getTerminator()));
|
|
EXPECT_EQ(BB3->getParent(), F);
|
|
DTU.recalculate(*F);
|
|
EXPECT_FALSE(DTU.hasPendingDeletedBB());
|
|
}
|
|
|
|
TEST(DomTreeUpdater, LazyUpdateDTInheritedPreds) {
|
|
StringRef FuncName = "f";
|
|
StringRef ModuleString = R"(
|
|
define i32 @f(i32 %i, i32 *%p) {
|
|
bb0:
|
|
store i32 %i, i32 *%p
|
|
switch i32 %i, label %bb1 [
|
|
i32 2, label %bb2
|
|
i32 3, label %bb3
|
|
]
|
|
bb1:
|
|
br label %bb3
|
|
bb2:
|
|
br label %bb3
|
|
bb3:
|
|
ret i32 3
|
|
}
|
|
)";
|
|
// Make the module.
|
|
LLVMContext Context;
|
|
std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString);
|
|
Function *F = M->getFunction(FuncName);
|
|
|
|
// Make the DTU.
|
|
DominatorTree DT(*F);
|
|
PostDominatorTree *PDT = nullptr;
|
|
DomTreeUpdater DTU(&DT, PDT, DomTreeUpdater::UpdateStrategy::Lazy);
|
|
ASSERT_TRUE(DTU.hasDomTree());
|
|
ASSERT_FALSE(DTU.hasPostDomTree());
|
|
ASSERT_FALSE(DTU.isEager());
|
|
ASSERT_TRUE(DTU.isLazy());
|
|
ASSERT_TRUE(DTU.getDomTree().verify());
|
|
|
|
Function::iterator FI = F->begin();
|
|
BasicBlock *BB0 = &*FI++;
|
|
BasicBlock *BB1 = &*FI++;
|
|
BasicBlock *BB2 = &*FI++;
|
|
BasicBlock *BB3 = &*FI++;
|
|
|
|
// There are several CFG locations where we have:
|
|
//
|
|
// pred1..predN
|
|
// | |
|
|
// +> curr <+ converted into: pred1..predN curr
|
|
// | | |
|
|
// v +> succ <+
|
|
// succ
|
|
//
|
|
// There is a specific shape of this we have to be careful of:
|
|
//
|
|
// pred1..predN
|
|
// || |
|
|
// |+> curr <+ converted into: pred1..predN curr
|
|
// | | | |
|
|
// | v +> succ <+
|
|
// +-> succ
|
|
//
|
|
// While the final CFG form is functionally identical the updates to
|
|
// DTU are not. In the first case we must have
|
|
// DTU.applyUpdates({{DominatorTree::Insert, Pred1, Succ}}) while in
|
|
// the latter case we must *NOT* have
|
|
// DTU.applyUpdates({{DominatorTree::Insert, Pred1, Succ}}).
|
|
|
|
// CFG Change: bb0 now only has bb0 -> bb1 and bb0 -> bb3. We are preparing to
|
|
// remove bb2.
|
|
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 3u);
|
|
BB0->getTerminator()->eraseFromParent();
|
|
BranchInst::Create(BB1, BB3, ConstantInt::getTrue(F->getContext()), BB0);
|
|
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 2u);
|
|
|
|
// Test callback utils.
|
|
std::vector<BasicBlock *> BasicBlocks;
|
|
BasicBlocks.push_back(BB1);
|
|
BasicBlocks.push_back(BB2);
|
|
auto Eraser = [&](BasicBlock *BB) {
|
|
BasicBlocks.erase(
|
|
std::remove_if(BasicBlocks.begin(), BasicBlocks.end(),
|
|
[&](const BasicBlock *i) { return i == BB; }),
|
|
BasicBlocks.end());
|
|
};
|
|
ASSERT_EQ(BasicBlocks.size(), static_cast<size_t>(2));
|
|
// Remove bb2 from F. This has to happen before the call to
|
|
// applyUpdates() for DTU to detect there is no longer an edge between
|
|
// bb2 -> bb3. The deleteBB() method converts bb2's TI into "unreachable".
|
|
ASSERT_FALSE(isa<UnreachableInst>(BB2->getTerminator()));
|
|
EXPECT_FALSE(DTU.isBBPendingDeletion(BB2));
|
|
DTU.callbackDeleteBB(BB2, Eraser);
|
|
EXPECT_TRUE(DTU.isBBPendingDeletion(BB2));
|
|
ASSERT_TRUE(isa<UnreachableInst>(BB2->getTerminator()));
|
|
EXPECT_EQ(BB2->getParent(), F);
|
|
|
|
// Queue up the DTU updates.
|
|
std::vector<DominatorTree::UpdateType> Updates;
|
|
Updates.reserve(4);
|
|
Updates.push_back({DominatorTree::Delete, BB0, BB2});
|
|
Updates.push_back({DominatorTree::Delete, BB2, BB3});
|
|
|
|
// Handle the specific shape case next.
|
|
// CFG Change: bb0 now only branches to bb3. We are preparing to remove bb1.
|
|
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 2u);
|
|
BB0->getTerminator()->eraseFromParent();
|
|
BranchInst::Create(BB3, BB0);
|
|
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 1u);
|
|
|
|
// Remove bb1 from F. This has to happen before the call to
|
|
// applyUpdates() for DTU to detect there is no longer an edge between
|
|
// bb1 -> bb3. The deleteBB() method converts bb1's TI into "unreachable".
|
|
ASSERT_FALSE(isa<UnreachableInst>(BB1->getTerminator()));
|
|
EXPECT_FALSE(DTU.isBBPendingDeletion(BB1));
|
|
DTU.callbackDeleteBB(BB1, Eraser);
|
|
EXPECT_TRUE(DTU.isBBPendingDeletion(BB1));
|
|
ASSERT_TRUE(isa<UnreachableInst>(BB1->getTerminator()));
|
|
EXPECT_EQ(BB1->getParent(), F);
|
|
|
|
// Update the DTU. In this case we don't submit {DominatorTree::Insert, BB0,
|
|
// BB3} because the edge previously existed at the start of this test when DT
|
|
// was first created.
|
|
Updates.push_back({DominatorTree::Delete, BB0, BB1});
|
|
Updates.push_back({DominatorTree::Delete, BB1, BB3});
|
|
|
|
// Verify everything.
|
|
DTU.applyUpdatesPermissive(Updates);
|
|
ASSERT_EQ(BasicBlocks.size(), static_cast<size_t>(2));
|
|
DTU.flush();
|
|
ASSERT_EQ(BasicBlocks.size(), static_cast<size_t>(0));
|
|
ASSERT_TRUE(DT.verify());
|
|
}
|
|
|
|
TEST(DomTreeUpdater, LazyUpdateBasicOperations) {
|
|
StringRef FuncName = "f";
|
|
StringRef ModuleString = R"(
|
|
define i32 @f(i32 %i, i32 *%p) {
|
|
bb0:
|
|
store i32 %i, i32 *%p
|
|
switch i32 %i, label %bb1 [
|
|
i32 0, label %bb2
|
|
i32 1, label %bb2
|
|
i32 2, label %bb3
|
|
]
|
|
bb1:
|
|
ret i32 1
|
|
bb2:
|
|
ret i32 2
|
|
bb3:
|
|
ret i32 3
|
|
}
|
|
)";
|
|
// Make the module.
|
|
LLVMContext Context;
|
|
std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString);
|
|
Function *F = M->getFunction(FuncName);
|
|
|
|
// Make the DTU.
|
|
DominatorTree DT(*F);
|
|
PostDominatorTree PDT(*F);
|
|
DomTreeUpdater DTU(&DT, &PDT, DomTreeUpdater::UpdateStrategy::Lazy);
|
|
ASSERT_TRUE(DTU.hasDomTree());
|
|
ASSERT_TRUE(DTU.hasPostDomTree());
|
|
ASSERT_FALSE(DTU.isEager());
|
|
ASSERT_TRUE(DTU.isLazy());
|
|
ASSERT_TRUE(DTU.getDomTree().verify());
|
|
ASSERT_TRUE(DTU.getPostDomTree().verify());
|
|
|
|
Function::iterator FI = F->begin();
|
|
BasicBlock *BB0 = &*FI++;
|
|
BasicBlock *BB1 = &*FI++;
|
|
BasicBlock *BB2 = &*FI++;
|
|
BasicBlock *BB3 = &*FI++;
|
|
// Test discards of self-domination update.
|
|
DTU.applyUpdates({{DominatorTree::Delete, BB0, BB0}});
|
|
|
|
// Delete edge bb0 -> bb3 and push the update twice to verify duplicate
|
|
// entries are discarded.
|
|
std::vector<DominatorTree::UpdateType> Updates;
|
|
Updates.reserve(4);
|
|
Updates.push_back({DominatorTree::Delete, BB0, BB3});
|
|
Updates.push_back({DominatorTree::Delete, BB0, BB3});
|
|
|
|
// Unnecessary Insert: no edge bb1 -> bb2 after change to bb0.
|
|
Updates.push_back({DominatorTree::Insert, BB1, BB2});
|
|
// Unnecessary Delete: edge exists bb0 -> bb1 after change to bb0.
|
|
Updates.push_back({DominatorTree::Delete, BB0, BB1});
|
|
|
|
// CFG Change: remove edge bb0 -> bb3 and one duplicate edge bb0 -> bb2.
|
|
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 4u);
|
|
BB0->getTerminator()->eraseFromParent();
|
|
BranchInst::Create(BB1, BB2, ConstantInt::getTrue(F->getContext()), BB0);
|
|
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 2u);
|
|
|
|
// Deletion of a BasicBlock is an immediate event. We remove all uses to the
|
|
// contained Instructions and change the Terminator to "unreachable" when
|
|
// queued for deletion. Its parent is still F until DTU.flushDomTree is
|
|
// called. We don't defer this action because it can cause problems for other
|
|
// transforms or analysis as it's part of the actual CFG. We only defer
|
|
// updates to the DominatorTree. This code will crash if it is placed before
|
|
// the BranchInst::Create() call above.
|
|
bool CallbackFlag = false;
|
|
ASSERT_FALSE(isa<UnreachableInst>(BB3->getTerminator()));
|
|
EXPECT_FALSE(DTU.isBBPendingDeletion(BB3));
|
|
DTU.callbackDeleteBB(BB3, [&](BasicBlock *) { CallbackFlag = true; });
|
|
EXPECT_TRUE(DTU.isBBPendingDeletion(BB3));
|
|
ASSERT_TRUE(isa<UnreachableInst>(BB3->getTerminator()));
|
|
EXPECT_EQ(BB3->getParent(), F);
|
|
|
|
// Verify. Updates to DTU must be applied *after* all changes to the CFG
|
|
// (including block deletion).
|
|
DTU.applyUpdatesPermissive(Updates);
|
|
ASSERT_TRUE(DTU.getDomTree().verify());
|
|
ASSERT_TRUE(DTU.hasPendingUpdates());
|
|
ASSERT_TRUE(DTU.hasPendingPostDomTreeUpdates());
|
|
ASSERT_FALSE(DTU.hasPendingDomTreeUpdates());
|
|
ASSERT_TRUE(DTU.hasPendingDeletedBB());
|
|
ASSERT_TRUE(DTU.getPostDomTree().verify());
|
|
ASSERT_FALSE(DTU.hasPendingUpdates());
|
|
ASSERT_FALSE(DTU.hasPendingPostDomTreeUpdates());
|
|
ASSERT_FALSE(DTU.hasPendingDomTreeUpdates());
|
|
ASSERT_FALSE(DTU.hasPendingDeletedBB());
|
|
ASSERT_EQ(CallbackFlag, true);
|
|
}
|
|
|
|
TEST(DomTreeUpdater, LazyUpdateReplaceEntryBB) {
|
|
StringRef FuncName = "f";
|
|
StringRef ModuleString = R"(
|
|
define i32 @f() {
|
|
bb0:
|
|
br label %bb1
|
|
bb1:
|
|
ret i32 1
|
|
}
|
|
)";
|
|
// Make the module.
|
|
LLVMContext Context;
|
|
std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString);
|
|
Function *F = M->getFunction(FuncName);
|
|
|
|
// Make the DTU.
|
|
DominatorTree DT(*F);
|
|
PostDominatorTree PDT(*F);
|
|
DomTreeUpdater DTU(DT, PDT, DomTreeUpdater::UpdateStrategy::Lazy);
|
|
ASSERT_TRUE(DTU.hasDomTree());
|
|
ASSERT_TRUE(DTU.hasPostDomTree());
|
|
ASSERT_FALSE(DTU.isEager());
|
|
ASSERT_TRUE(DTU.isLazy());
|
|
ASSERT_TRUE(DTU.getDomTree().verify());
|
|
ASSERT_TRUE(DTU.getPostDomTree().verify());
|
|
|
|
Function::iterator FI = F->begin();
|
|
BasicBlock *BB0 = &*FI++;
|
|
BasicBlock *BB1 = &*FI++;
|
|
|
|
// Add a block as the new function entry BB. We also link it to BB0.
|
|
BasicBlock *NewEntry =
|
|
BasicBlock::Create(F->getContext(), "new_entry", F, BB0);
|
|
BranchInst::Create(BB0, NewEntry);
|
|
EXPECT_EQ(F->begin()->getName(), NewEntry->getName());
|
|
EXPECT_TRUE(&F->getEntryBlock() == NewEntry);
|
|
|
|
// Insert the new edge between new_entry -> bb0. Without this the
|
|
// recalculate() call below will not actually recalculate the DT as there
|
|
// are no changes pending and no blocks deleted.
|
|
DTU.applyUpdates({{DominatorTree::Insert, NewEntry, BB0}});
|
|
|
|
// Changing the Entry BB requires a full recalculation.
|
|
DTU.recalculate(*F);
|
|
ASSERT_TRUE(DTU.getDomTree().verify());
|
|
ASSERT_TRUE(DTU.getPostDomTree().verify());
|
|
|
|
// CFG Change: remove new_edge -> bb0 and redirect to new_edge -> bb1.
|
|
EXPECT_EQ(NewEntry->getTerminator()->getNumSuccessors(), 1u);
|
|
NewEntry->getTerminator()->eraseFromParent();
|
|
BranchInst::Create(BB1, NewEntry);
|
|
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 1u);
|
|
|
|
// Update the DTU. At this point bb0 now has no predecessors but is still a
|
|
// Child of F.
|
|
DTU.applyUpdates({{DominatorTree::Delete, NewEntry, BB0},
|
|
{DominatorTree::Insert, NewEntry, BB1}});
|
|
DTU.flush();
|
|
ASSERT_TRUE(DT.verify());
|
|
ASSERT_TRUE(PDT.verify());
|
|
|
|
// Now remove bb0 from F.
|
|
ASSERT_FALSE(isa<UnreachableInst>(BB0->getTerminator()));
|
|
EXPECT_FALSE(DTU.isBBPendingDeletion(BB0));
|
|
DTU.deleteBB(BB0);
|
|
EXPECT_TRUE(DTU.isBBPendingDeletion(BB0));
|
|
ASSERT_TRUE(isa<UnreachableInst>(BB0->getTerminator()));
|
|
EXPECT_EQ(BB0->getParent(), F);
|
|
|
|
// Perform a full recalculation of the DTU. It is not necessary here but we
|
|
// do this to test the case when there are no pending DT updates but there are
|
|
// pending deleted BBs.
|
|
ASSERT_TRUE(DTU.hasPendingDeletedBB());
|
|
DTU.recalculate(*F);
|
|
ASSERT_FALSE(DTU.hasPendingDeletedBB());
|
|
}
|
|
|
|
TEST(DomTreeUpdater, LazyUpdateStepTest) {
|
|
// This test focus on testing a DTU holding both trees applying multiple
|
|
// updates and DT/PDT not flushed together.
|
|
StringRef FuncName = "f";
|
|
StringRef ModuleString = R"(
|
|
define i32 @f(i32 %i, i32 *%p) {
|
|
bb0:
|
|
store i32 %i, i32 *%p
|
|
switch i32 %i, label %bb1 [
|
|
i32 0, label %bb1
|
|
i32 1, label %bb2
|
|
i32 2, label %bb3
|
|
i32 3, label %bb1
|
|
]
|
|
bb1:
|
|
ret i32 1
|
|
bb2:
|
|
ret i32 2
|
|
bb3:
|
|
ret i32 3
|
|
}
|
|
)";
|
|
// Make the module.
|
|
LLVMContext Context;
|
|
std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString);
|
|
Function *F = M->getFunction(FuncName);
|
|
|
|
// Make the DomTreeUpdater.
|
|
DominatorTree DT(*F);
|
|
PostDominatorTree PDT(*F);
|
|
DomTreeUpdater DTU(DT, PDT, DomTreeUpdater::UpdateStrategy::Lazy);
|
|
|
|
ASSERT_TRUE(DTU.hasDomTree());
|
|
ASSERT_TRUE(DTU.hasPostDomTree());
|
|
ASSERT_FALSE(DTU.isEager());
|
|
ASSERT_TRUE(DTU.isLazy());
|
|
ASSERT_TRUE(DTU.getDomTree().verify());
|
|
ASSERT_TRUE(DTU.getPostDomTree().verify());
|
|
ASSERT_FALSE(DTU.hasPendingUpdates());
|
|
|
|
Function::iterator FI = F->begin();
|
|
BasicBlock *BB0 = &*FI++;
|
|
FI++;
|
|
BasicBlock *BB2 = &*FI++;
|
|
BasicBlock *BB3 = &*FI++;
|
|
SwitchInst *SI = dyn_cast<SwitchInst>(BB0->getTerminator());
|
|
ASSERT_NE(SI, nullptr) << "Couldn't get SwitchInst.";
|
|
|
|
// Delete edge bb0 -> bb3.
|
|
std::vector<DominatorTree::UpdateType> Updates;
|
|
Updates.reserve(1);
|
|
Updates.push_back({DominatorTree::Delete, BB0, BB3});
|
|
|
|
// CFG Change: remove edge bb0 -> bb3.
|
|
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 5u);
|
|
BB3->removePredecessor(BB0);
|
|
for (auto i = SI->case_begin(), e = SI->case_end(); i != e; ++i) {
|
|
if (i->getCaseIndex() == 2) {
|
|
SI->removeCase(i);
|
|
break;
|
|
}
|
|
}
|
|
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 4u);
|
|
// Deletion of a BasicBlock is an immediate event. We remove all uses to the
|
|
// contained Instructions and change the Terminator to "unreachable" when
|
|
// queued for deletion.
|
|
ASSERT_FALSE(isa<UnreachableInst>(BB3->getTerminator()));
|
|
EXPECT_FALSE(DTU.isBBPendingDeletion(BB3));
|
|
DTU.applyUpdates(Updates);
|
|
|
|
// Only flush DomTree.
|
|
ASSERT_TRUE(DTU.getDomTree().verify());
|
|
ASSERT_TRUE(DTU.hasPendingPostDomTreeUpdates());
|
|
ASSERT_FALSE(DTU.hasPendingDomTreeUpdates());
|
|
|
|
ASSERT_EQ(BB3->getParent(), F);
|
|
DTU.deleteBB(BB3);
|
|
|
|
Updates.clear();
|
|
|
|
// Remove all case branch to BB2 to test Eager recalculation.
|
|
// Code section from llvm::ConstantFoldTerminator
|
|
for (auto i = SI->case_begin(), e = SI->case_end(); i != e;) {
|
|
if (i->getCaseSuccessor() == BB2) {
|
|
// Remove this entry.
|
|
BB2->removePredecessor(BB0);
|
|
i = SI->removeCase(i);
|
|
e = SI->case_end();
|
|
Updates.push_back({DominatorTree::Delete, BB0, BB2});
|
|
} else
|
|
++i;
|
|
}
|
|
|
|
DTU.applyUpdatesPermissive(Updates);
|
|
// flush PostDomTree
|
|
ASSERT_TRUE(DTU.getPostDomTree().verify());
|
|
ASSERT_FALSE(DTU.hasPendingPostDomTreeUpdates());
|
|
ASSERT_TRUE(DTU.hasPendingDomTreeUpdates());
|
|
// flush both trees
|
|
DTU.flush();
|
|
ASSERT_TRUE(DT.verify());
|
|
}
|
|
|
|
TEST(DomTreeUpdater, NoTreeTest) {
|
|
StringRef FuncName = "f";
|
|
StringRef ModuleString = R"(
|
|
define i32 @f() {
|
|
bb0:
|
|
ret i32 0
|
|
}
|
|
)";
|
|
// Make the module.
|
|
LLVMContext Context;
|
|
std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString);
|
|
Function *F = M->getFunction(FuncName);
|
|
|
|
// Make the DTU.
|
|
DomTreeUpdater DTU(nullptr, nullptr, DomTreeUpdater::UpdateStrategy::Lazy);
|
|
ASSERT_FALSE(DTU.hasDomTree());
|
|
ASSERT_FALSE(DTU.hasPostDomTree());
|
|
Function::iterator FI = F->begin();
|
|
BasicBlock *BB0 = &*FI++;
|
|
// Test whether PendingDeletedBB is flushed after the recalculation.
|
|
DTU.deleteBB(BB0);
|
|
ASSERT_TRUE(DTU.hasPendingDeletedBB());
|
|
DTU.recalculate(*F);
|
|
ASSERT_FALSE(DTU.hasPendingDeletedBB());
|
|
}
|
|
|
|
TEST(DomTreeUpdater, LazyUpdateDeduplicationTest) {
|
|
StringRef FuncName = "f";
|
|
StringRef ModuleString = R"(
|
|
define i32 @f() {
|
|
bb0:
|
|
br label %bb1
|
|
bb1:
|
|
ret i32 1
|
|
bb2:
|
|
ret i32 1
|
|
}
|
|
)";
|
|
// Make the module.
|
|
LLVMContext Context;
|
|
std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString);
|
|
Function *F = M->getFunction(FuncName);
|
|
|
|
// Make the DTU.
|
|
DominatorTree DT(*F);
|
|
DomTreeUpdater DTU(&DT, nullptr, DomTreeUpdater::UpdateStrategy::Lazy);
|
|
ASSERT_TRUE(DTU.getDomTree().verify());
|
|
|
|
Function::iterator FI = F->begin();
|
|
BasicBlock *BB0 = &*FI++;
|
|
BasicBlock *BB1 = &*FI++;
|
|
BasicBlock *BB2 = &*FI++;
|
|
|
|
// CFG Change: remove bb0 -> bb1 and add back bb0 -> bb1.
|
|
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 1u);
|
|
BB0->getTerminator()->eraseFromParent();
|
|
BranchInst::Create(BB1, BB0);
|
|
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 1u);
|
|
|
|
// Update the DTU and simulate duplicates.
|
|
DTU.applyUpdatesPermissive({{DominatorTree::Delete, BB0, BB1},
|
|
{DominatorTree::Delete, BB0, BB1},
|
|
{DominatorTree::Insert, BB0, BB1},
|
|
{DominatorTree::Insert, BB0, BB1},
|
|
{DominatorTree::Insert, BB0, BB1}});
|
|
|
|
// The above operations result in a no-op.
|
|
ASSERT_FALSE(DTU.hasPendingUpdates());
|
|
|
|
// Update the DTU. Simulate an invalid update.
|
|
DTU.applyUpdatesPermissive({{DominatorTree::Delete, BB0, BB1}});
|
|
ASSERT_FALSE(DTU.hasPendingUpdates());
|
|
|
|
// CFG Change: remove bb0 -> bb1.
|
|
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 1u);
|
|
BB0->getTerminator()->eraseFromParent();
|
|
|
|
// Update the DTU and simulate invalid updates.
|
|
DTU.applyUpdatesPermissive({{DominatorTree::Delete, BB0, BB1},
|
|
{DominatorTree::Insert, BB0, BB1},
|
|
{DominatorTree::Delete, BB0, BB1},
|
|
{DominatorTree::Insert, BB0, BB1},
|
|
{DominatorTree::Insert, BB0, BB1}});
|
|
ASSERT_TRUE(DTU.hasPendingUpdates());
|
|
|
|
// CFG Change: add bb0 -> bb2.
|
|
BranchInst::Create(BB2, BB0);
|
|
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 1u);
|
|
DTU.applyUpdates({{DominatorTree::Insert, BB0, BB2}});
|
|
ASSERT_TRUE(DTU.getDomTree().verify());
|
|
}
|