| //===- 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()); |
| } |