[llvm] [GVNSink] Fix non-determinisms by using Depth-First ordering (PR #90995)

via llvm-commits llvm-commits at lists.llvm.org
Fri May 3 15:26:51 PDT 2024


https://github.com/hiraditya updated https://github.com/llvm/llvm-project/pull/90995

>From 057b4e0a864aa45d732b083e16b9c2e52e42a3ae Mon Sep 17 00:00:00 2001
From: AdityaK <hiraditya at msn.com>
Date: Thu, 2 May 2024 16:08:51 -0700
Subject: [PATCH 1/2] Fix non-determinisms

GVNSink used to order instructions based on their pointer values and was prone to
non-determinism because of that. Using DFSNumber for ordering all the values
fixes the non-determinism
---
 llvm/lib/Transforms/Scalar/GVNSink.cpp        | 65 +++++++++++++++----
 .../test/Transforms/GVNSink/int_sideeffect.ll | 26 ++++++++
 2 files changed, 77 insertions(+), 14 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/GVNSink.cpp b/llvm/lib/Transforms/Scalar/GVNSink.cpp
index d4907326eb0a5a..c6613f427d8ece 100644
--- a/llvm/lib/Transforms/Scalar/GVNSink.cpp
+++ b/llvm/lib/Transforms/Scalar/GVNSink.cpp
@@ -222,20 +222,29 @@ raw_ostream &operator<<(raw_ostream &OS, const SinkingInstructionCandidate &C) {
 class ModelledPHI {
   SmallVector<Value *, 4> Values;
   SmallVector<BasicBlock *, 4> Blocks;
+  DominatorTree *DT;
 
 public:
   ModelledPHI() = default;
 
-  ModelledPHI(const PHINode *PN) {
+  ModelledPHI(const PHINode *PN, DominatorTree *DT) : DT(DT) {
     // BasicBlock comes first so we sort by basic block pointer order, then by value pointer order.
-    SmallVector<std::pair<BasicBlock *, Value *>, 4> Ops;
+    using OpsType = std::pair<BasicBlock *, Value *>;
+    SmallVector<OpsType, 4> Ops;
     for (unsigned I = 0, E = PN->getNumIncomingValues(); I != E; ++I)
       Ops.push_back({PN->getIncomingBlock(I), PN->getIncomingValue(I)});
-    llvm::sort(Ops);
+
+    auto DFSOrder = [DT](OpsType O1, OpsType O2) {
+      return DT->getNode(O1.first) < DT->getNode(O2.first);
+    };
+    // Sort by DFSNumber to have a deterministic order.
+    llvm::sort(Ops, DFSOrder);
+
     for (auto &P : Ops) {
       Blocks.push_back(P.first);
       Values.push_back(P.second);
     }
+    verifyModelledPHI();
   }
 
   /// Create a dummy ModelledPHI that will compare unequal to any other ModelledPHI
@@ -247,19 +256,37 @@ class ModelledPHI {
     return M;
   }
 
+  void verifyModelledPHI() {
+    assert(Values.size() > 1 && Blocks.size() > 1 &&
+           "Modelling PHI with less than 2 values");
+    auto DFSOrder = [this](const BasicBlock *BB1, const BasicBlock *BB2) {
+      return this->DT->getNode(BB1) < this->DT->getNode(BB2);
+    };
+    assert(llvm::is_sorted(Blocks, DFSOrder));
+    int C = 0;
+    llvm::for_each(Values, [&C, this](const Value *V) {
+      const Instruction *I = cast<Instruction>(V);
+      assert(I->getParent() == this->Blocks[C++]);
+    });
+  }
   /// Create a PHI from an array of incoming values and incoming blocks.
-  template <typename VArray, typename BArray>
-  ModelledPHI(const VArray &V, const BArray &B) {
+  ModelledPHI(SmallVectorImpl<Instruction *> &V,
+              SmallSetVector<BasicBlock *, 4> &B, DominatorTree *DT)
+      : DT(DT) {
+    // The order of Values and Blocks are already as per their DFSNumbers.
     llvm::copy(V, std::back_inserter(Values));
     llvm::copy(B, std::back_inserter(Blocks));
+    verifyModelledPHI();
   }
 
   /// Create a PHI from [I[OpNum] for I in Insts].
-  template <typename BArray>
-  ModelledPHI(ArrayRef<Instruction *> Insts, unsigned OpNum, const BArray &B) {
+  ModelledPHI(ArrayRef<Instruction *> Insts, unsigned OpNum,
+              SmallSetVector<BasicBlock *, 4> &B, DominatorTree *DT)
+      : DT(DT) {
     llvm::copy(B, std::back_inserter(Blocks));
     for (auto *I : Insts)
       Values.push_back(I->getOperand(OpNum));
+    verifyModelledPHI();
   }
 
   /// Restrict the PHI's contents down to only \c NewBlocks.
@@ -297,7 +324,8 @@ class ModelledPHI {
 
   // Hash functor
   unsigned hash() const {
-      return (unsigned)hash_combine_range(Values.begin(), Values.end());
+    // Is deterministic because Values are saved in DFSOrder.
+    return (unsigned)hash_combine_range(Values.begin(), Values.end());
   }
 
   bool operator==(const ModelledPHI &Other) const {
@@ -566,7 +594,7 @@ class ValueTable {
 
 class GVNSink {
 public:
-  GVNSink() = default;
+  GVNSink(DominatorTree *DT) : DT(DT) {}
 
   bool run(Function &F) {
     LLVM_DEBUG(dbgs() << "GVNSink: running on function @" << F.getName()
@@ -583,6 +611,7 @@ class GVNSink {
 
 private:
   ValueTable VN;
+  DominatorTree *DT;
 
   bool shouldAvoidSinkingInstruction(Instruction *I) {
     // These instructions may change or break semantics if moved.
@@ -603,7 +632,7 @@ class GVNSink {
   void analyzeInitialPHIs(BasicBlock *BB, ModelledPHISet &PHIs,
                           SmallPtrSetImpl<Value *> &PHIContents) {
     for (PHINode &PN : BB->phis()) {
-      auto MPHI = ModelledPHI(&PN);
+      auto MPHI = ModelledPHI(&PN, DT);
       PHIs.insert(MPHI);
       for (auto *V : MPHI.getValues())
         PHIContents.insert(V);
@@ -691,7 +720,7 @@ GVNSink::analyzeInstructionForSinking(LockstepReverseIterator &LRI,
   }
 
   // The sunk instruction's results.
-  ModelledPHI NewPHI(NewInsts, ActivePreds);
+  ModelledPHI NewPHI(NewInsts, ActivePreds, DT);
 
   // Does sinking this instruction render previous PHIs redundant?
   if (NeededPHIs.erase(NewPHI))
@@ -728,7 +757,7 @@ GVNSink::analyzeInstructionForSinking(LockstepReverseIterator &LRI,
     return std::nullopt;
 
   for (unsigned OpNum = 0, E = I0->getNumOperands(); OpNum != E; ++OpNum) {
-    ModelledPHI PHI(NewInsts, OpNum, ActivePreds);
+    ModelledPHI PHI(NewInsts, OpNum, ActivePreds, DT);
     if (PHI.areAllIncomingValuesSame())
       continue;
     if (!canReplaceOperandWithVariable(I0, OpNum))
@@ -774,7 +803,11 @@ unsigned GVNSink::sinkBB(BasicBlock *BBEnd) {
   }
   if (Preds.size() < 2)
     return 0;
-  llvm::sort(Preds);
+  auto DFSOrder = [this](const BasicBlock *BB1, const BasicBlock *BB2) {
+    return this->DT->getNode(BB1) < this->DT->getNode(BB2);
+  };
+  // Sort by DFSNumber to have a deterministic order.
+  llvm::sort(Preds, DFSOrder);
 
   unsigned NumOrigPreds = Preds.size();
   // We can only sink instructions through unconditional branches.
@@ -886,8 +919,12 @@ void GVNSink::sinkLastInstruction(ArrayRef<BasicBlock *> Blocks,
 } // end anonymous namespace
 
 PreservedAnalyses GVNSinkPass::run(Function &F, FunctionAnalysisManager &AM) {
-  GVNSink G;
+  auto &DT = AM.getResult<DominatorTreeAnalysis>(F);
+  GVNSink G(&DT);
   if (!G.run(F))
     return PreservedAnalyses::all();
+
+  // PHI nodes get inserted which haven't been added to the Dominator Tree.
+  // FIXME: Update DominatorTree to account for sunk instructions.
   return PreservedAnalyses::none();
 }
diff --git a/llvm/test/Transforms/GVNSink/int_sideeffect.ll b/llvm/test/Transforms/GVNSink/int_sideeffect.ll
index 3cc54e84f17c1b..9a3bc062dd9462 100644
--- a/llvm/test/Transforms/GVNSink/int_sideeffect.ll
+++ b/llvm/test/Transforms/GVNSink/int_sideeffect.ll
@@ -28,3 +28,29 @@ if.end:
   ret float %phi
 }
 
+; CHECK-LABEL: scalarsSinkingReverse
+; CHECK-NOT: fmul
+; CHECK: = phi
+; CHECK: = fmul
+define float @scalarsSinkingReverse(float %d, float %m, float %a, i1 %cmp) {
+; This test is just a reverse(graph mirror) of the test
+; above to ensure GVNSink doesn't depend on the order of branches.
+entry:
+  br i1 %cmp, label %if.then, label %if.else
+
+if.then:
+  %add = fadd float %m, %a
+  %mul1 = fmul float %add, %d
+  br label %if.end
+
+if.else:
+  call void @llvm.sideeffect()
+  %sub = fsub float %m, %a
+  %mul0 = fmul float %sub, %d
+  br label %if.end
+
+if.end:
+  %phi = phi float [ %mul1, %if.then ], [ %mul0, %if.else ]
+  ret float %phi
+}
+

>From 093d79d6e5e83ee41a1a7c08519229f9fe461e64 Mon Sep 17 00:00:00 2001
From: AdityaK <hiraditya at msn.com>
Date: Fri, 3 May 2024 15:25:37 -0700
Subject: [PATCH 2/2] Fix issues with DT update, use DFSNumbers

---
 llvm/lib/Transforms/Scalar/GVNSink.cpp | 35 +++++++++++++++++---------
 1 file changed, 23 insertions(+), 12 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/GVNSink.cpp b/llvm/lib/Transforms/Scalar/GVNSink.cpp
index c6613f427d8ece..b7cf37e2369a13 100644
--- a/llvm/lib/Transforms/Scalar/GVNSink.cpp
+++ b/llvm/lib/Transforms/Scalar/GVNSink.cpp
@@ -42,6 +42,7 @@
 #include "llvm/ADT/SmallPtrSet.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/Statistic.h"
+#include "llvm/Analysis/DomTreeUpdater.h"
 #include "llvm/Analysis/GlobalsModRef.h"
 #include "llvm/IR/BasicBlock.h"
 #include "llvm/IR/CFG.h"
@@ -228,14 +229,16 @@ class ModelledPHI {
   ModelledPHI() = default;
 
   ModelledPHI(const PHINode *PN, DominatorTree *DT) : DT(DT) {
-    // BasicBlock comes first so we sort by basic block pointer order, then by value pointer order.
+    // BasicBlock comes first so we sort by basic block pointer order,
+    // then by value pointer order. No need to call `verifyModelledPHI`
+    // As the Values and Blocks are populated in DFSOrder already.
     using OpsType = std::pair<BasicBlock *, Value *>;
     SmallVector<OpsType, 4> Ops;
     for (unsigned I = 0, E = PN->getNumIncomingValues(); I != E; ++I)
       Ops.push_back({PN->getIncomingBlock(I), PN->getIncomingValue(I)});
 
     auto DFSOrder = [DT](OpsType O1, OpsType O2) {
-      return DT->getNode(O1.first) < DT->getNode(O2.first);
+      return DT->getNode(O1.first)->getDFSNumIn() < DT->getNode(O2.first)->getDFSNumIn();
     };
     // Sort by DFSNumber to have a deterministic order.
     llvm::sort(Ops, DFSOrder);
@@ -244,7 +247,6 @@ class ModelledPHI {
       Blocks.push_back(P.first);
       Values.push_back(P.second);
     }
-    verifyModelledPHI();
   }
 
   /// Create a dummy ModelledPHI that will compare unequal to any other ModelledPHI
@@ -260,13 +262,16 @@ class ModelledPHI {
     assert(Values.size() > 1 && Blocks.size() > 1 &&
            "Modelling PHI with less than 2 values");
     auto DFSOrder = [this](const BasicBlock *BB1, const BasicBlock *BB2) {
-      return this->DT->getNode(BB1) < this->DT->getNode(BB2);
+      return DT->getNode(BB1)->getDFSNumIn() < DT->getNode(BB2)->getDFSNumIn();
     };
     assert(llvm::is_sorted(Blocks, DFSOrder));
     int C = 0;
     llvm::for_each(Values, [&C, this](const Value *V) {
-      const Instruction *I = cast<Instruction>(V);
-      assert(I->getParent() == this->Blocks[C++]);
+      if (!isa<UndefValue>(V)) {
+        const Instruction *I = cast<Instruction>(V);
+        assert(I->getParent() == this->Blocks[C]);
+      }
+      C++;
     });
   }
   /// Create a PHI from an array of incoming values and incoming blocks.
@@ -280,13 +285,13 @@ class ModelledPHI {
   }
 
   /// Create a PHI from [I[OpNum] for I in Insts].
+  /// TODO: Figure out a way to verifyModelledPHI in this constructor.
   ModelledPHI(ArrayRef<Instruction *> Insts, unsigned OpNum,
               SmallSetVector<BasicBlock *, 4> &B, DominatorTree *DT)
       : DT(DT) {
     llvm::copy(B, std::back_inserter(Blocks));
     for (auto *I : Insts)
       Values.push_back(I->getOperand(OpNum));
-    verifyModelledPHI();
   }
 
   /// Restrict the PHI's contents down to only \c NewBlocks.
@@ -795,6 +800,9 @@ unsigned GVNSink::sinkBB(BasicBlock *BBEnd) {
              BBEnd->printAsOperand(dbgs()); dbgs() << "\n");
   SmallVector<BasicBlock *, 4> Preds;
   for (auto *B : predecessors(BBEnd)) {
+    // Bailout on malformed CFG where BasicBlock has no predecessor(PR42346).
+    if (!DT->getNode(B))
+      return 0;
     auto *T = B->getTerminator();
     if (isa<BranchInst>(T) || isa<SwitchInst>(T))
       Preds.push_back(B);
@@ -804,7 +812,8 @@ unsigned GVNSink::sinkBB(BasicBlock *BBEnd) {
   if (Preds.size() < 2)
     return 0;
   auto DFSOrder = [this](const BasicBlock *BB1, const BasicBlock *BB2) {
-    return this->DT->getNode(BB1) < this->DT->getNode(BB2);
+    return DT->getNode(BB1)->getDFSNumIn() <
+           DT->getNode(BB2)->getDFSNumIn();
   };
   // Sort by DFSNumber to have a deterministic order.
   llvm::sort(Preds, DFSOrder);
@@ -844,11 +853,12 @@ unsigned GVNSink::sinkBB(BasicBlock *BBEnd) {
   auto C = Candidates.front();
 
   LLVM_DEBUG(dbgs() << " -- Sinking: " << C << "\n");
+  DomTreeUpdater DTU(DT, DomTreeUpdater::UpdateStrategy::Eager);
   BasicBlock *InsertBB = BBEnd;
   if (C.Blocks.size() < NumOrigPreds) {
     LLVM_DEBUG(dbgs() << " -- Splitting edge to ";
                BBEnd->printAsOperand(dbgs()); dbgs() << "\n");
-    InsertBB = SplitBlockPredecessors(BBEnd, C.Blocks, ".gvnsink.split");
+    InsertBB = SplitBlockPredecessors(BBEnd, C.Blocks, ".gvnsink.split", &DTU);
     if (!InsertBB) {
       LLVM_DEBUG(dbgs() << " -- FAILED to split edge!\n");
       // Edge couldn't be split.
@@ -921,10 +931,11 @@ void GVNSink::sinkLastInstruction(ArrayRef<BasicBlock *> Blocks,
 PreservedAnalyses GVNSinkPass::run(Function &F, FunctionAnalysisManager &AM) {
   auto &DT = AM.getResult<DominatorTreeAnalysis>(F);
   GVNSink G(&DT);
+  DT.updateDFSNumbers();
   if (!G.run(F))
     return PreservedAnalyses::all();
 
-  // PHI nodes get inserted which haven't been added to the Dominator Tree.
-  // FIXME: Update DominatorTree to account for sunk instructions.
-  return PreservedAnalyses::none();
+  PreservedAnalyses PA;
+  PA.preserve<DominatorTreeAnalysis>();
+  return PA;
 }



More information about the llvm-commits mailing list