[llvm] [SimplifyCFG] Introduce a heuristic code sinker to fold phi expressions (PR #128171)

via llvm-commits llvm-commits at lists.llvm.org
Fri Feb 21 05:20:01 PST 2025


https://github.com/u4f3 created https://github.com/llvm/llvm-project/pull/128171

close #127365 

Try to sink instructions from other blocks.

>From fa7465801d71567e4f3644199984d3aa05b93f28 Mon Sep 17 00:00:00 2001
From: u4f3 <ricoafoat at gmail.com>
Date: Fri, 21 Feb 2025 21:16:54 +0800
Subject: [PATCH] [SimplifyCFG] Introduce a heuristic code sinker to fold phi
 expressions

---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp | 186 +++++++++++++++++++---
 1 file changed, 166 insertions(+), 20 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 27b7ec4629a26..8a5ed0440fd0c 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -2325,24 +2325,11 @@ static bool canSinkInstructions(
   return true;
 }
 
-// Assuming canSinkInstructions(Blocks) has returned true, sink the last
-// instruction of every block in Blocks to their common successor, commoning
-// into one instruction.
-static void sinkLastInstruction(ArrayRef<BasicBlock*> Blocks) {
-  auto *BBEnd = Blocks[0]->getTerminator()->getSuccessor(0);
-
-  // canSinkInstructions returning true guarantees that every block has at
-  // least one non-terminator instruction.
-  SmallVector<Instruction*,4> Insts;
-  for (auto *BB : Blocks) {
-    Instruction *I = BB->getTerminator();
-    do {
-      I = I->getPrevNode();
-    } while (isa<DbgInfoIntrinsic>(I) && I != &BB->front());
-    if (!isa<DbgInfoIntrinsic>(I))
-      Insts.push_back(I);
-  }
-
+static void sinkCandidatesImpl(BasicBlock *BBEnd,
+                               ArrayRef<BasicBlock *> IncomingBBs,
+                               ArrayRef<Instruction *> Insts) {
+  assert(IncomingBBs.size() == Insts.size() &&
+         "We already guarentee they are the same size!");
   // We don't need to do any more checking here; canSinkInstructions should
   // have done it all for us.
   SmallVector<Value*, 4> NewOperands;
@@ -2368,8 +2355,8 @@ static void sinkLastInstruction(ArrayRef<BasicBlock*> Blocks) {
     auto *PN =
         PHINode::Create(Op->getType(), Insts.size(), Op->getName() + ".sink");
     PN->insertBefore(BBEnd->begin());
-    for (auto *I : Insts)
-      PN->addIncoming(I->getOperand(O), I->getParent());
+    for (decltype(Insts.size()) i = 0; i < Insts.size(); i++)
+      PN->addIncoming(Insts[i]->getOperand(O), IncomingBBs[i]);
     NewOperands.push_back(PN);
   }
 
@@ -2423,6 +2410,27 @@ static void sinkLastInstruction(ArrayRef<BasicBlock*> Blocks) {
   }
 }
 
+// Assuming canSinkInstructions(Blocks) has returned true, sink the last
+// instruction of every block in Blocks to their common successor, commoning
+// into one instruction.
+static bool sinkLastInstruction(ArrayRef<BasicBlock *> Blocks) {
+  auto *BBEnd = Blocks[0]->getTerminator()->getSuccessor(0);
+
+  // canSinkInstructions returning true guarantees that every block has at
+  // least one non-terminator instruction.
+  SmallVector<Instruction *, 4> Insts;
+  for (auto *BB : Blocks) {
+    Instruction *I = BB->getTerminator();
+    do {
+      I = I->getPrevNode();
+    } while (isa<DbgInfoIntrinsic>(I) && I != &BB->front());
+    if (!isa<DbgInfoIntrinsic>(I))
+      Insts.push_back(I);
+  }
+  sinkCandidatesImpl(BBEnd, Blocks, Insts);
+  return true;
+}
+
 /// Check whether BB's predecessors end with unconditional branches. If it is
 /// true, sink any common code from the predecessors to BB.
 static bool sinkCommonCodeFromPredecessors(BasicBlock *BB,
@@ -2683,6 +2691,143 @@ static bool sinkCommonCodeFromPredecessors(BasicBlock *BB,
   return Changed;
 }
 
+using SinkCandidatesType = std::vector<std::vector<Instruction *>>;
+using HerusticGetCandidatesCallback = std::function<bool(
+    SinkCandidatesType &, bool &, BasicBlock *, DomTreeUpdater *)>;
+
+static bool transformAllocaToIsomorphicGEP(PHINode &PN) {
+  // We need at least one GEP to construct a gep with zero offset for
+  // over-optimized alloca, if any.
+  GetElementPtrInst *oneGEP = nullptr;
+  for (auto &IncomingValue : PN.incoming_values())
+    if ((oneGEP = dyn_cast<GetElementPtrInst>(IncomingValue)))
+      break;
+
+  // If there is no GEP, we can't sink the instruction.
+  if (oneGEP == nullptr)
+    return false;
+
+  SmallMapVector<AllocaInst *, GetElementPtrInst *, 4> AllocaToIsomorphicGEP;
+  auto &EntryBB = PN.getParent()->getParent()->getEntryBlock();
+  auto NumOperands = oneGEP->getNumOperands();
+  for (auto &IncomingValue : PN.incoming_values()) {
+    if (auto AI = dyn_cast<AllocaInst>(IncomingValue)) {
+      if (!AI->isStaticAlloca())
+        return false;
+      if (AllocaToIsomorphicGEP.find(AI) != AllocaToIsomorphicGEP.end())
+        continue;
+      if (oneGEP->getOperand(0) != AI)
+        return false;
+
+      auto IsomorphicGEP = dyn_cast<GetElementPtrInst>(oneGEP->clone());
+      for (unsigned i = 1; i < NumOperands; ++i) {
+        auto IntTy = IsomorphicGEP->getOperand(i)->getType();
+        IsomorphicGEP->setOperand(i, ConstantInt::get(IntTy, 0));
+      }
+      IsomorphicGEP->insertBefore(EntryBB.getTerminator());
+      AllocaToIsomorphicGEP[AI] = IsomorphicGEP;
+    }
+  }
+
+  for (unsigned I = 0, E = PN.getNumIncomingValues(); I < E; ++I) {
+    auto IncomingValue = PN.getIncomingValue(I);
+    if (auto AI = dyn_cast<AllocaInst>(IncomingValue)) {
+      auto IsomorphicGEP = AllocaToIsomorphicGEP[AI];
+      PN.setIncomingValue(I, IsomorphicGEP);
+    }
+  }
+
+  return AllocaToIsomorphicGEP.size() > 0;
+}
+
+static bool
+herusticGetPhiGEPCandidate(SinkCandidatesType &ResultsSinkCandidates,
+                           bool &Changed, BasicBlock *BB, DomTreeUpdater *DTU) {
+
+  auto Preds = std::vector<BasicBlock *>();
+  for (auto *PredBB : predecessors(BB))
+    Preds.push_back(PredBB);
+
+  if (Preds.size() < 2)
+    return false;
+
+  DenseMap<const Use *, SmallVector<Value *, 4>> PHIOperands;
+  for (PHINode &PN : BB->phis()) {
+    Changed |= transformAllocaToIsomorphicGEP(PN);
+    SmallDenseMap<BasicBlock *, const Use *, 4> IncomingVals;
+    for (const Use &U : PN.incoming_values())
+      IncomingVals.insert({PN.getIncomingBlock(U), &U});
+    auto &Ops = PHIOperands[IncomingVals[Preds[0]]];
+    for (BasicBlock *Pred : Preds)
+      Ops.push_back(*IncomingVals[Pred]);
+  }
+
+  SinkCandidatesType SinkCandidates;
+  for (auto &[U, IncomingValues] : PHIOperands) {
+    auto CorrespondingPhiNode = dyn_cast<PHINode>(U->getUser());
+    assert(CorrespondingPhiNode &&
+           "PHIOperands should only contain PHINode's operands.");
+
+    std::vector<Instruction *> Candidates;
+
+    for (auto IncomingValue : IncomingValues) {
+      if (auto GEP = dyn_cast<GetElementPtrInst>(IncomingValue)) {
+        Candidates.push_back(GEP);
+      } else {
+        Candidates.clear();
+        break;
+      }
+    }
+
+    if (Candidates.empty())
+      continue;
+
+    auto BasePtr = Candidates[0]->getOperand(0);
+    for (auto Candidate : Candidates) {
+      if (Candidate->getOperand(0) != BasePtr) {
+        Candidates.clear();
+        break;
+      }
+    }
+
+    if (Candidates.empty())
+      continue;
+
+    SinkCandidates.emplace_back(std::move(Candidates));
+  }
+
+  for (auto &Candidates : SinkCandidates)
+    if (canSinkInstructions(Candidates, PHIOperands))
+      ResultsSinkCandidates.emplace_back(std::move(Candidates));
+
+  if (ResultsSinkCandidates.size() != 0)
+    return true;
+  return false;
+}
+
+static bool herusticPhiSinker(BasicBlock *BB, DomTreeUpdater *DTU,
+                              HerusticGetCandidatesCallback GetCandidates) {
+  SinkCandidatesType SinkCandidates;
+  bool Changed = false;
+  if (!GetCandidates(SinkCandidates, Changed, BB, DTU) ||
+      SinkCandidates.size() == 0)
+    return Changed;
+
+  std::vector<BasicBlock *> Preds;
+  for (auto *PredBB : predecessors(BB))
+    Preds.push_back(PredBB);
+
+  for (auto Candidates : SinkCandidates) {
+    LLVM_DEBUG(dbgs() << "SINK: Sink: " << *Candidates[0] << "\n");
+    sinkCandidatesImpl(BB, Preds, Candidates);
+    NumSinkCommonInstrs++;
+    Changed = true;
+  }
+  if (SinkCandidates.size() != 0)
+    ++NumSinkCommonCode;
+  return Changed;
+}
+
 namespace {
 
 struct CompatibleSets {
@@ -8380,6 +8525,7 @@ bool SimplifyCFGOpt::simplifyOnce(BasicBlock *BB) {
 
   if (SinkCommon && Options.SinkCommonInsts)
     if (sinkCommonCodeFromPredecessors(BB, DTU) ||
+        herusticPhiSinker(BB, DTU, herusticGetPhiGEPCandidate) ||
         mergeCompatibleInvokes(BB, DTU)) {
       // sinkCommonCodeFromPredecessors() does not automatically CSE PHI's,
       // so we may now how duplicate PHI's.



More information about the llvm-commits mailing list