#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
})";
LLVMContext Context;
std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString);
Function *F = M->getFunction(FuncName);
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());
std::vector<DominatorTree::UpdateType> Updates;
Updates.reserve(4);
Updates.push_back({DominatorTree::Delete, BB0, BB3});
Updates.push_back({DominatorTree::Delete, BB0, BB3});
Updates.push_back({DominatorTree::Insert, BB1, BB2});
Updates.push_back({DominatorTree::Delete, BB0, BB1});
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);
ASSERT_FALSE(isa<UnreachableInst>(BB3->getTerminator()));
EXPECT_FALSE(DTU.isBBPendingDeletion(BB3));
DTU.applyUpdatesPermissive(Updates);
ASSERT_FALSE(DTU.hasPendingUpdates());
DTU.applyUpdatesPermissive(
{{DominatorTree::Insert, BB1, BB2}, {DominatorTree::Delete, BB0, BB1}});
ASSERT_TRUE(DT.verify());
ASSERT_TRUE(PDT.verify());
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());
DTU.flush();
EXPECT_TRUE(DT.verify());
EXPECT_TRUE(PDT.verify());
for (auto i = SI->case_begin(), e = SI->case_end(); i != e;) {
if (i->getCaseSuccessor() == BB2) {
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
}
)";
LLVMContext Context;
std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString);
Function *F = M->getFunction(FuncName);
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++;
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}});
DTU.recalculate(*F);
ASSERT_TRUE(DT.verify());
ASSERT_TRUE(PDT.verify());
EXPECT_EQ(NewEntry->getTerminator()->getNumSuccessors(), 1u);
NewEntry->getTerminator()->eraseFromParent();
BranchInst::Create(BB1, NewEntry);
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 1u);
DTU.applyUpdates({{DominatorTree::Delete, NewEntry, BB0},
{DominatorTree::Insert, NewEntry, BB1}});
ASSERT_TRUE(DT.verify());
ASSERT_TRUE(PDT.verify());
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
}
)";
LLVMContext Context;
std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString);
Function *F = M->getFunction(FuncName);
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++;
DTU.applyUpdatesPermissive({{DominatorTree::Insert, BB0, BB0}});
ASSERT_FALSE(DTU.hasPendingDomTreeUpdates());
std::vector<DominatorTree::UpdateType> Updates;
Updates.reserve(4);
Updates.push_back({DominatorTree::Delete, BB0, BB3});
Updates.push_back({DominatorTree::Delete, BB0, BB3});
Updates.push_back({DominatorTree::Insert, BB1, BB2});
Updates.push_back({DominatorTree::Delete, BB0, BB1});
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 4u);
BB0->getTerminator()->eraseFromParent();
BranchInst::Create(BB1, BB2, ConstantInt::getTrue(F->getContext()), BB0);
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 2u);
DTU.applyUpdatesPermissive(Updates);
ASSERT_TRUE(DTU.getDomTree().verify());
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
}
)";
LLVMContext Context;
std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString);
Function *F = M->getFunction(FuncName);
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++;
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 3u);
BB0->getTerminator()->eraseFromParent();
BranchInst::Create(BB1, BB3, ConstantInt::getTrue(F->getContext()), BB0);
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 2u);
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));
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);
std::vector<DominatorTree::UpdateType> Updates;
Updates.reserve(4);
Updates.push_back({DominatorTree::Delete, BB0, BB2});
Updates.push_back({DominatorTree::Delete, BB2, BB3});
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 2u);
BB0->getTerminator()->eraseFromParent();
BranchInst::Create(BB3, BB0);
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 1u);
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);
Updates.push_back({DominatorTree::Delete, BB0, BB1});
Updates.push_back({DominatorTree::Delete, BB1, BB3});
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
}
)";
LLVMContext Context;
std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString);
Function *F = M->getFunction(FuncName);
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++;
DTU.applyUpdates({{DominatorTree::Delete, BB0, BB0}});
std::vector<DominatorTree::UpdateType> Updates;
Updates.reserve(4);
Updates.push_back({DominatorTree::Delete, BB0, BB3});
Updates.push_back({DominatorTree::Delete, BB0, BB3});
Updates.push_back({DominatorTree::Insert, BB1, BB2});
Updates.push_back({DominatorTree::Delete, BB0, BB1});
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 4u);
BB0->getTerminator()->eraseFromParent();
BranchInst::Create(BB1, BB2, ConstantInt::getTrue(F->getContext()), BB0);
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 2u);
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);
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
}
)";
LLVMContext Context;
std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString);
Function *F = M->getFunction(FuncName);
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 *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}});
DTU.recalculate(*F);
ASSERT_TRUE(DTU.getDomTree().verify());
ASSERT_TRUE(DTU.getPostDomTree().verify());
EXPECT_EQ(NewEntry->getTerminator()->getNumSuccessors(), 1u);
NewEntry->getTerminator()->eraseFromParent();
BranchInst::Create(BB1, NewEntry);
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 1u);
DTU.applyUpdates({{DominatorTree::Delete, NewEntry, BB0},
{DominatorTree::Insert, NewEntry, BB1}});
DTU.flush();
ASSERT_TRUE(DT.verify());
ASSERT_TRUE(PDT.verify());
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);
ASSERT_TRUE(DTU.hasPendingDeletedBB());
DTU.recalculate(*F);
ASSERT_FALSE(DTU.hasPendingDeletedBB());
}
TEST(DomTreeUpdater, LazyUpdateStepTest) {
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
}
)";
LLVMContext Context;
std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString);
Function *F = M->getFunction(FuncName);
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.";
std::vector<DominatorTree::UpdateType> Updates;
Updates.reserve(1);
Updates.push_back({DominatorTree::Delete, 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);
ASSERT_FALSE(isa<UnreachableInst>(BB3->getTerminator()));
EXPECT_FALSE(DTU.isBBPendingDeletion(BB3));
DTU.applyUpdates(Updates);
ASSERT_TRUE(DTU.getDomTree().verify());
ASSERT_TRUE(DTU.hasPendingPostDomTreeUpdates());
ASSERT_FALSE(DTU.hasPendingDomTreeUpdates());
ASSERT_EQ(BB3->getParent(), F);
DTU.deleteBB(BB3);
Updates.clear();
for (auto i = SI->case_begin(), e = SI->case_end(); i != e;) {
if (i->getCaseSuccessor() == BB2) {
BB2->removePredecessor(BB0);
i = SI->removeCase(i);
e = SI->case_end();
Updates.push_back({DominatorTree::Delete, BB0, BB2});
} else
++i;
}
DTU.applyUpdatesPermissive(Updates);
ASSERT_TRUE(DTU.getPostDomTree().verify());
ASSERT_FALSE(DTU.hasPendingPostDomTreeUpdates());
ASSERT_TRUE(DTU.hasPendingDomTreeUpdates());
DTU.flush();
ASSERT_TRUE(DT.verify());
}
TEST(DomTreeUpdater, NoTreeTest) {
StringRef FuncName = "f";
StringRef ModuleString = R"(
define i32 @f() {
bb0:
ret i32 0
}
)";
LLVMContext Context;
std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString);
Function *F = M->getFunction(FuncName);
DomTreeUpdater DTU(nullptr, nullptr, DomTreeUpdater::UpdateStrategy::Lazy);
ASSERT_FALSE(DTU.hasDomTree());
ASSERT_FALSE(DTU.hasPostDomTree());
Function::iterator FI = F->begin();
BasicBlock *BB0 = &*FI++;
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
}
)";
LLVMContext Context;
std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString);
Function *F = M->getFunction(FuncName);
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++;
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 1u);
BB0->getTerminator()->eraseFromParent();
BranchInst::Create(BB1, BB0);
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 1u);
DTU.applyUpdatesPermissive({{DominatorTree::Delete, BB0, BB1},
{DominatorTree::Delete, BB0, BB1},
{DominatorTree::Insert, BB0, BB1},
{DominatorTree::Insert, BB0, BB1},
{DominatorTree::Insert, BB0, BB1}});
ASSERT_FALSE(DTU.hasPendingUpdates());
DTU.applyUpdatesPermissive({{DominatorTree::Delete, BB0, BB1}});
ASSERT_FALSE(DTU.hasPendingUpdates());
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 1u);
BB0->getTerminator()->eraseFromParent();
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());
BranchInst::Create(BB2, BB0);
EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 1u);
DTU.applyUpdates({{DominatorTree::Insert, BB0, BB2}});
ASSERT_TRUE(DTU.getDomTree().verify());
}