llvm.org GIT mirror llvm / 82f51d5
[Dominators] Implement incremental deletions Summary: This patch implements incremental edge deletions. It also makes DominatorTreeBase store a pointer to the parent function. The parent function is needed to perform full rebuilts during some deletions, but it is also used to verify that inserted and deleted edges come from the same function. Reviewers: dberlin, davide, grosser, sanjoy, brzycki Reviewed By: dberlin Subscribers: llvm-commits Differential Revision: https://reviews.llvm.org/D35342 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@308062 91177308-0d34-0410-b5e6-96231b3b80d8 Jakub Kuderski 2 years ago
5 changed file(s) with 396 addition(s) and 11 deletion(s). Raw diff Collapse all Expand all
4747 extern template void InsertEdge(BBDomTree &DT, BasicBlock *From,
4848 BasicBlock *To);
4949 extern template void InsertEdge(BBPostDomTree &DT,
50 BasicBlock *From,
51 BasicBlock *To);
52
53 extern template void DeleteEdge(BBDomTree &DT, BasicBlock *From,
54 BasicBlock *To);
55 extern template void DeleteEdge(BBPostDomTree &DT,
5056 BasicBlock *From,
5157 BasicBlock *To);
5258
193193 void InsertEdge(DomTreeT &DT, typename DomTreeT::NodePtr From,
194194 typename DomTreeT::NodePtr To);
195195
196 template
197 void DeleteEdge(DomTreeT &DT, typename DomTreeT::NodePtr From,
198 typename DomTreeT::NodePtr To);
199
196200 template
197201 bool Verify(const DomTreeT &DT);
198202 } // namespace DomTreeBuilder
210214 DenseMap>>;
211215 DomTreeNodeMapType DomTreeNodes;
212216 DomTreeNodeBase *RootNode;
217 using ParentPtr = decltype(std::declval()->getParent());
218 ParentPtr Parent = nullptr;
213219
214220 mutable bool DFSInfoValid = false;
215221 mutable unsigned int SlowQueries = 0;
228234 DominatorTreeBase(DominatorTreeBase &&Arg)
229235 : Roots(std::move(Arg.Roots)),
230236 DomTreeNodes(std::move(Arg.DomTreeNodes)),
231 RootNode(std::move(Arg.RootNode)),
232 DFSInfoValid(std::move(Arg.DFSInfoValid)),
233 SlowQueries(std::move(Arg.SlowQueries)) {
237 RootNode(Arg.RootNode),
238 Parent(Arg.Parent),
239 DFSInfoValid(Arg.DFSInfoValid),
240 SlowQueries(Arg.SlowQueries) {
234241 Arg.wipe();
235242 }
236243
237244 DominatorTreeBase &operator=(DominatorTreeBase &&RHS) {
238245 Roots = std::move(RHS.Roots);
239246 DomTreeNodes = std::move(RHS.DomTreeNodes);
240 RootNode = std::move(RHS.RootNode);
241 DFSInfoValid = std::move(RHS.DFSInfoValid);
242 SlowQueries = std::move(RHS.SlowQueries);
247 RootNode = RHS.RootNode;
248 Parent = RHS.Parent;
249 DFSInfoValid = RHS.DFSInfoValid;
250 SlowQueries = RHS.SlowQueries;
243251 RHS.wipe();
244252 return *this;
245253 }
260268 /// compare - Return false if the other dominator tree base matches this
261269 /// dominator tree base. Otherwise return true.
262270 bool compare(const DominatorTreeBase &Other) const {
271 if (Parent != Other.Parent) return true;
263272
264273 const DomTreeNodeMapType &OtherDomTreeNodes = Other.DomTreeNodes;
265274 if (DomTreeNodes.size() != OtherDomTreeNodes.size())
452461 /// This function has to be called just before or just after making the update
453462 /// on the actual CFG. There cannot be any other updates that the dominator
454463 /// tree doesn't know about.
464 ///
455465 /// Note that for postdominators it automatically takes care of inserting
456466 /// a reverse edge internally (so there's no need to swap the parameters).
457467 ///
458468 void insertEdge(NodeT *From, NodeT *To) {
459469 assert(From);
460470 assert(To);
471 assert(From->getParent() == Parent);
472 assert(To->getParent() == Parent);
461473 DomTreeBuilder::InsertEdge(*this, From, To);
474 }
475
476 /// Inform the dominator tree about a CFG edge deletion and update the tree.
477 ///
478 /// This function has to be called just after making the update
479 /// on the actual CFG. There cannot be any other updates that the dominator
480 /// tree doesn't know about. The only exception is when the deletion that the
481 /// tree is informed about makes some (domominator) subtree unreachable -- in
482 /// this case, it is fine to perform deletions within this subtree.
483 ///
484 /// Note that for postdominators it automatically takes care of deleting
485 /// a reverse edge internally (so there's no need to swap the parameters).
486 ///
487 void deleteEdge(NodeT *From, NodeT *To) {
488 assert(From);
489 assert(To);
490 assert(From->getParent() == Parent);
491 assert(To->getParent() == Parent);
492 DomTreeBuilder::DeleteEdge(*this, From, To);
462493 }
463494
464495 /// Add a new node to the dominator tree information.
621652 template void recalculate(FT &F) {
622653 using TraitsTy = GraphTraits;
623654 reset();
655 Parent = &F;
624656
625657 if (!IsPostDominator) {
626658 // Initialize root
646678 DomTreeNodes.clear();
647679 Roots.clear();
648680 RootNode = nullptr;
681 Parent = nullptr;
649682 DFSInfoValid = false;
650683 SlowQueries = 0;
651684 }
727760 void wipe() {
728761 DomTreeNodes.clear();
729762 RootNode = nullptr;
763 Parent = nullptr;
730764 }
731765 };
732766
105105 // Add a new tree node for this NodeT, and link it as a child of
106106 // IDomNode
107107 return (DT.DomTreeNodes[BB] = IDomNode->addChild(
108 llvm::make_unique>(BB, IDomNode)))
108 llvm::make_unique>(BB, IDomNode)))
109109 .get();
110110 }
111111
326326 }
327327 }
328328
329 void reattachExistingSubtree(DomTreeT &DT, const TreeNodePtr AttachTo) {
330 NodeToInfo[NumToNode[1]].IDom = AttachTo->getBlock();
331 for (size_t i = 1, e = NumToNode.size(); i != e; ++i) {
332 const NodePtr N = NumToNode[i];
333 const TreeNodePtr TN = DT.getNode(N);
334 assert(TN);
335 const TreeNodePtr NewIDom = DT.getNode(NodeToInfo[N].IDom);
336 TN->setIDom(NewIDom);
337 }
338 }
339
329340 // Helper struct used during edge insertions.
330341 struct InsertionInfo {
331342 using BucketElementTy = std::pair;
337348 };
338349
339350 std::priority_queue,
340 DecreasingLevel>
351 DecreasingLevel>
341352 Bucket; // Queue of tree nodes sorted by level in descending order.
342353 SmallDenseSet Affected;
343354 SmallDenseSet Visited;
414425
415426 assert(TN->getBlock());
416427 for (const NodePtr Succ :
417 ChildrenGetter::Get(TN->getBlock())) {
428 ChildrenGetter::Get(TN->getBlock())) {
418429 const TreeNodePtr SuccTN = DT.getNode(Succ);
419430 assert(SuccTN && "Unreachable successor found at reachable insertion");
420431 const unsigned SuccLevel = SuccTN->getLevel();
467478 }
468479 }
469480
470 // Handles insertion to previousely unreachable nodes.
481 // Handles insertion to previously unreachable nodes.
471482 static void InsertUnreachable(DomTreeT &DT, const TreeNodePtr From,
472483 const NodePtr To) {
473484 DEBUG(dbgs() << "Inserting " << BlockNamePrinter(From)
497508 static void ComputeUnreachableDominators(
498509 DomTreeT &DT, const NodePtr Root, const TreeNodePtr Incoming,
499510 SmallVectorImpl>
500 &DiscoveredConnectingEdges) {
511 &DiscoveredConnectingEdges) {
501512 assert(!DT.getNode(Root) && "Root must not be reachable");
502513
503514 // Visit only previously unreachable nodes.
552563
553564 return true;
554565 }
566
567 static void DeleteEdge(DomTreeT &DT, const NodePtr From, const NodePtr To) {
568 assert(From && To && "Cannot disconnect nullptrs");
569 DEBUG(dbgs() << "Deleting edge " << BlockNamePrinter(From) << " -> "
570 << BlockNamePrinter(To) << "\n");
571 const TreeNodePtr FromTN = DT.getNode(From);
572 // Deletion in an unreachable subtree -- nothing to do.
573 if (!FromTN) return;
574
575 const TreeNodePtr ToTN = DT.getNode(To);
576 const NodePtr NCDBlock = DT.findNearestCommonDominator(From, To);
577 const TreeNodePtr NCD = DT.getNode(NCDBlock);
578
579 // To dominates From -- nothing to do.
580 if (ToTN == NCD) return;
581
582 const TreeNodePtr ToIDom = ToTN->getIDom();
583 DEBUG(dbgs() << "\tNCD " << BlockNamePrinter(NCD) << ", ToIDom "
584 << BlockNamePrinter(ToIDom) << "\n");
585
586 // To remains reachable after deletion.
587 // (Based on the caption under Figure 4. from the second paper.)
588 if (FromTN != ToIDom || HasProperSupport(DT, ToTN))
589 DeleteReachable(DT, FromTN, ToTN);
590 else
591 DeleteUnreachable(DT, ToTN);
592 }
593
594 // Handles deletions that leave destination nodes reachable.
595 static void DeleteReachable(DomTreeT &DT, const TreeNodePtr FromTN,
596 const TreeNodePtr ToTN) {
597 DEBUG(dbgs() << "Deleting reachable " << BlockNamePrinter(FromTN) << " -> "
598 << BlockNamePrinter(ToTN) << "\n");
599 DEBUG(dbgs() << "\tRebuilding subtree\n");
600
601 // Find the top of the subtree that needs to be rebuilt.
602 // (Based on the lemma 2.6 from the second paper.)
603 const NodePtr ToIDom =
604 DT.findNearestCommonDominator(FromTN->getBlock(), ToTN->getBlock());
605 assert(ToIDom || DT.isPostDominator());
606 const TreeNodePtr ToIDomTN = DT.getNode(ToIDom);
607 assert(ToIDomTN);
608 const TreeNodePtr PrevIDomSubTree = ToIDomTN->getIDom();
609 // Top of the subtree to rebuild is the root node. Rebuild the tree from
610 // scratch.
611 if (!PrevIDomSubTree) {
612 DEBUG(dbgs() << "The entire tree needs to be rebuilt\n");
613 DT.recalculate(*DT.Parent);
614 return;
615 }
616
617 // Only visit nodes in the subtree starting at To.
618 const unsigned Level = ToIDomTN->getLevel();
619 auto DescendBelow = [Level, &DT](NodePtr, NodePtr To) {
620 return DT.getNode(To)->getLevel() > Level;
621 };
622
623 DEBUG(dbgs() << "\tTop of subtree: " << BlockNamePrinter(ToIDomTN) << "\n");
624
625 SemiNCAInfo SNCA;
626 SNCA.runDFS(ToIDom, 0, DescendBelow, 0);
627 DEBUG(dbgs() << "\tRunning Semi-NCA\n");
628 SNCA.runSemiNCA(DT, Level);
629 SNCA.reattachExistingSubtree(DT, PrevIDomSubTree);
630 }
631
632 // Checks if a node has proper support, as defined on the page 3 and later
633 // explained on the page 7 of the second paper.
634 static bool HasProperSupport(DomTreeT &DT, const TreeNodePtr TN) {
635 DEBUG(dbgs() << "IsReachableFromIDom " << BlockNamePrinter(TN) << "\n");
636 for (const NodePtr Pred :
637 ChildrenGetter::Get(TN->getBlock())) {
638 DEBUG(dbgs() << "\tPred " << BlockNamePrinter(Pred) << "\n");
639 if (!DT.getNode(Pred)) continue;
640
641 const NodePtr Support =
642 DT.findNearestCommonDominator(TN->getBlock(), Pred);
643 DEBUG(dbgs() << "\tSupport " << BlockNamePrinter(Support) << "\n");
644 if (Support != TN->getBlock()) {
645 DEBUG(dbgs() << "\t" << BlockNamePrinter(TN)
646 << " is reachable from support "
647 << BlockNamePrinter(Support) << "\n");
648 return true;
649 }
650 }
651
652 return false;
653 }
654
655 // Handle deletions that make destination node unreachable.
656 // (Based on the lemma 2.7 from the second paper.)
657 static void DeleteUnreachable(DomTreeT &DT, const TreeNodePtr ToTN) {
658 DEBUG(dbgs() << "Deleting unreachable subtree " << BlockNamePrinter(ToTN)
659 << "\n");
660 assert(ToTN);
661 assert(ToTN->getBlock());
662
663 SmallVector AffectedQueue;
664 const unsigned Level = ToTN->getLevel();
665
666 // Traverse destination node's descendants with greater level in the tree
667 // and collect visited nodes.
668 auto DescendAndCollect = [Level, &AffectedQueue, &DT](NodePtr, NodePtr To) {
669 const TreeNodePtr TN = DT.getNode(To);
670 assert(TN);
671 if (TN->getLevel() > Level) return true;
672 if (llvm::find(AffectedQueue, To) == AffectedQueue.end())
673 AffectedQueue.push_back(To);
674
675 return false;
676 };
677
678 SemiNCAInfo SNCA;
679 unsigned LastDFSNum =
680 SNCA.runDFS(ToTN->getBlock(), 0, DescendAndCollect, 0);
681
682 TreeNodePtr MinNode = ToTN;
683
684 // Identify the top of the subtree to rebuilt by finding the NCD of all
685 // the affected nodes.
686 for (const NodePtr N : AffectedQueue) {
687 const TreeNodePtr TN = DT.getNode(N);
688 const NodePtr NCDBlock =
689 DT.findNearestCommonDominator(TN->getBlock(), ToTN->getBlock());
690 assert(NCDBlock || DT.isPostDominator());
691 const TreeNodePtr NCD = DT.getNode(NCDBlock);
692 assert(NCD);
693
694 DEBUG(dbgs() << "Processing affected node " << BlockNamePrinter(TN)
695 << " with NCD = " << BlockNamePrinter(NCD)
696 << ", MinNode =" << BlockNamePrinter(MinNode) << "\n");
697 if (NCD != TN && NCD->getLevel() < MinNode->getLevel()) MinNode = NCD;
698 }
699
700 // Root reached, rebuild the whole tree from scratch.
701 if (!MinNode->getIDom()) {
702 DEBUG(dbgs() << "The entire tree needs to be rebuilt\n");
703 DT.recalculate(*DT.Parent);
704 return;
705 }
706
707 // Erase the unreachable subtree in reverse preorder to process all children
708 // before deleting their parent.
709 for (unsigned i = LastDFSNum; i > 0; --i) {
710 const NodePtr N = SNCA.NumToNode[i];
711 const TreeNodePtr TN = DT.getNode(N);
712 DEBUG(dbgs() << "Erasing node " << BlockNamePrinter(TN) << "\n");
713
714 EraseNode(DT, TN);
715 }
716
717 // The affected subtree start at the To node -- there's no extra work to do.
718 if (MinNode == ToTN) return;
719
720 DEBUG(dbgs() << "DeleteUnreachable: running DFS with MinNode = "
721 << BlockNamePrinter(MinNode) << "\n");
722 const unsigned MinLevel = MinNode->getLevel();
723 const TreeNodePtr PrevIDom = MinNode->getIDom();
724 assert(PrevIDom);
725 SNCA.clear();
726
727 // Identify nodes that remain in the affected subtree.
728 auto DescendBelow = [MinLevel, &DT](NodePtr, NodePtr To) {
729 const TreeNodePtr ToTN = DT.getNode(To);
730 return ToTN && ToTN->getLevel() > MinLevel;
731 };
732 SNCA.runDFS(MinNode->getBlock(), 0, DescendBelow, 0);
733
734 DEBUG(dbgs() << "Previous IDom(MinNode) = " << BlockNamePrinter(PrevIDom)
735 << "\nRunning Semi-NCA\n");
736
737 // Rebuild the remaining part of affected subtree.
738 SNCA.runSemiNCA(DT, MinLevel);
739 SNCA.reattachExistingSubtree(DT, PrevIDom);
740 }
741
742 // Removes leaf tree nodes from the dominator tree.
743 static void EraseNode(DomTreeT &DT, const TreeNodePtr TN) {
744 assert(TN);
745 assert(TN->getNumChildren() == 0 && "Not a tree leaf");
746
747 const TreeNodePtr IDom = TN->getIDom();
748 assert(IDom);
749
750 auto ChIt = llvm::find(IDom->Children, TN);
751 assert(ChIt != IDom->Children.end());
752 std::swap(*ChIt, IDom->Children.back());
753 IDom->Children.pop_back();
754
755 DT.DomTreeNodes.erase(TN->getBlock());
756 }
757
758 //~~
759 //===--------------- DomTree correctness verification ---------------------===
760 //~~
555761
556762 // Check if for every parent with a level L in the tree all of its children
557763 // have level L + 1.
739945 }
740946
741947 template
948 void DeleteEdge(DomTreeT &DT, typename DomTreeT::NodePtr From,
949 typename DomTreeT::NodePtr To) {
950 if (DT.isPostDominator()) std::swap(From, To);
951 SemiNCAInfo::DeleteEdge(DT, From, To);
952 }
953
954 template
742955 bool Verify(const DomTreeT &DT) {
743956 SemiNCAInfo SNCA;
744957 return SNCA.verifyReachability(DT) && SNCA.VerifyLevels(DT) &&
7373 template void llvm::DomTreeBuilder::InsertEdge(
7474 DomTreeBuilder::BBDomTree &DT, BasicBlock *From, BasicBlock *To);
7575 template void llvm::DomTreeBuilder::InsertEdge(
76 DomTreeBuilder::BBPostDomTree &DT, BasicBlock *From, BasicBlock *To);
77
78 template void llvm::DomTreeBuilder::DeleteEdge(
79 DomTreeBuilder::BBDomTree &DT, BasicBlock *From, BasicBlock *To);
80 template void llvm::DomTreeBuilder::DeleteEdge(
7681 DomTreeBuilder::BBPostDomTree &DT, BasicBlock *From, BasicBlock *To);
7782
7883 template bool llvm::DomTreeBuilder::Verify(
326326
327327 namespace {
328328 const auto Insert = CFGBuilder::ActionKind::Insert;
329 const auto Delete = CFGBuilder::ActionKind::Delete;
329330
330331 bool CompUpdates(const CFGBuilder::Update &A, const CFGBuilder::Update &B) {
331332 return std::tie(A.Action, A.Edge.From, A.Edge.To) <
447448 }
448449 }
449450 }
451
452 TEST(DominatorTree, DeleteReachable) {
453 CFGHolder Holder;
454 std::vector Arcs = {
455 {"1", "2"}, {"2", "3"}, {"2", "4"}, {"3", "4"}, {"4", "5"}, {"5", "6"},
456 {"5", "7"}, {"7", "8"}, {"3", "8"}, {"8", "9"}, {"9", "10"}, {"10", "2"}};
457
458 std::vector Updates = {
459 {Delete, {"2", "4"}}, {Delete, {"7", "8"}}, {Delete, {"10", "2"}}};
460 CFGBuilder B(Holder.F, Arcs, Updates);
461 DominatorTree DT(*Holder.F);
462 EXPECT_TRUE(DT.verify());
463 PostDomTree PDT(*Holder.F);
464 EXPECT_TRUE(PDT.verify());
465
466 Optional LastUpdate;
467 while ((LastUpdate = B.applyUpdate())) {
468 EXPECT_EQ(LastUpdate->Action, Delete);
469 BasicBlock *From = B.getOrAddBlock(LastUpdate->Edge.From);
470 BasicBlock *To = B.getOrAddBlock(LastUpdate->Edge.To);
471 DT.deleteEdge(From, To);
472 EXPECT_TRUE(DT.verify());
473 PDT.deleteEdge(From, To);
474 EXPECT_TRUE(PDT.verify());
475 }
476 }
477
478 TEST(DominatorTree, DeleteUnreachable) {
479 CFGHolder Holder;
480 std::vector Arcs = {
481 {"1", "2"}, {"2", "3"}, {"3", "4"}, {"4", "5"}, {"5", "6"}, {"5", "7"},
482 {"7", "8"}, {"3", "8"}, {"8", "9"}, {"9", "10"}, {"10", "2"}};
483
484 std::vector Updates = {
485 {Delete, {"8", "9"}}, {Delete, {"7", "8"}}, {Delete, {"3", "4"}}};
486 CFGBuilder B(Holder.F, Arcs, Updates);
487 DominatorTree DT(*Holder.F);
488 EXPECT_TRUE(DT.verify());
489 PostDomTree PDT(*Holder.F);
490 EXPECT_TRUE(PDT.verify());
491
492 Optional LastUpdate;
493 while ((LastUpdate = B.applyUpdate())) {
494 EXPECT_EQ(LastUpdate->Action, Delete);
495 BasicBlock *From = B.getOrAddBlock(LastUpdate->Edge.From);
496 BasicBlock *To = B.getOrAddBlock(LastUpdate->Edge.To);
497 DT.deleteEdge(From, To);
498 EXPECT_TRUE(DT.verify());
499 PDT.deleteEdge(From, To);
500 EXPECT_TRUE(PDT.verify());
501 }
502 }
503
504 TEST(DominatorTree, InsertDelete) {
505 std::vector Arcs = {
506 {"1", "2"}, {"2", "3"}, {"3", "4"}, {"4", "5"}, {"5", "6"}, {"5", "7"},
507 {"3", "8"}, {"8", "9"}, {"9", "10"}, {"8", "11"}, {"11", "12"}};
508
509 std::vector Updates = {
510 {Insert, {"2", "4"}}, {Insert, {"12", "10"}}, {Insert, {"10", "9"}},
511 {Insert, {"7", "6"}}, {Insert, {"7", "5"}}, {Delete, {"3", "8"}},
512 {Insert, {"10", "7"}}, {Insert, {"2", "8"}}, {Delete, {"3", "4"}},
513 {Delete, {"8", "9"}}, {Delete, {"11", "12"}}};
514
515 CFGHolder Holder;
516 CFGBuilder B(Holder.F, Arcs, Updates);
517 DominatorTree DT(*Holder.F);
518 EXPECT_TRUE(DT.verify());
519 PostDomTree PDT(*Holder.F);
520 EXPECT_TRUE(PDT.verify());
521
522 Optional LastUpdate;
523 while ((LastUpdate = B.applyUpdate())) {
524 BasicBlock *From = B.getOrAddBlock(LastUpdate->Edge.From);
525 BasicBlock *To = B.getOrAddBlock(LastUpdate->Edge.To);
526 if (LastUpdate->Action == Insert) {
527 DT.insertEdge(From, To);
528 PDT.insertEdge(From, To);
529 } else {
530 DT.deleteEdge(From, To);
531 PDT.deleteEdge(From, To);
532 }
533
534 EXPECT_TRUE(DT.verify());
535 EXPECT_TRUE(PDT.verify());
536 }
537 }
538
539 TEST(DominatorTree, InsertDeleteExhaustive) {
540 std::vector Arcs = {
541 {"1", "2"}, {"2", "3"}, {"3", "4"}, {"4", "5"}, {"5", "6"}, {"5", "7"},
542 {"3", "8"}, {"8", "9"}, {"9", "10"}, {"8", "11"}, {"11", "12"}};
543
544 std::vector Updates = {
545 {Insert, {"2", "4"}}, {Insert, {"12", "10"}}, {Insert, {"10", "9"}},
546 {Insert, {"7", "6"}}, {Insert, {"7", "5"}}, {Delete, {"3", "8"}},
547 {Insert, {"10", "7"}}, {Insert, {"2", "8"}}, {Delete, {"3", "4"}},
548 {Delete, {"8", "9"}}, {Delete, {"11", "12"}}};
549
550 std::mt19937 Generator(0);
551 for (unsigned i = 0; i < 16; ++i) {
552 std::shuffle(Updates.begin(), Updates.end(), Generator);
553 CFGHolder Holder;
554 CFGBuilder B(Holder.F, Arcs, Updates);
555 DominatorTree DT(*Holder.F);
556 EXPECT_TRUE(DT.verify());
557 PostDomTree PDT(*Holder.F);
558 EXPECT_TRUE(PDT.verify());
559
560 Optional LastUpdate;
561 while ((LastUpdate = B.applyUpdate())) {
562 BasicBlock *From = B.getOrAddBlock(LastUpdate->Edge.From);
563 BasicBlock *To = B.getOrAddBlock(LastUpdate->Edge.To);
564 if (LastUpdate->Action == Insert) {
565 DT.insertEdge(From, To);
566 PDT.insertEdge(From, To);
567 } else {
568 DT.deleteEdge(From, To);
569 PDT.deleteEdge(From, To);
570 }
571
572 EXPECT_TRUE(DT.verify());
573 EXPECT_TRUE(PDT.verify());
574 }
575 }
576 }