Skip to content

Conversation

vpykhtin
Copy link
Contributor

This patch, along with #131762, enhances SSAUpdaterBulk to function as a near drop-in replacement for SSAUpdater, see #130611.

I opted to implement phi optimization as a separate step to maintain the current behavior of SSAUpdaterBulk::RewriteAllUses while ensuring the implementation remains straightforward.

@llvmbot
Copy link
Member

llvmbot commented Mar 19, 2025

@llvm/pr-subscribers-backend-webassembly

@llvm/pr-subscribers-llvm-transforms

Author: Valery Pykhtin (vpykhtin)

Changes

This patch, along with #131762, enhances SSAUpdaterBulk to function as a near drop-in replacement for SSAUpdater, see #130611.

I opted to implement phi optimization as a separate step to maintain the current behavior of SSAUpdaterBulk::RewriteAllUses while ensuring the implementation remains straightforward.


Full diff: https://github.com/llvm/llvm-project/pull/132004.diff

2 Files Affected:

  • (modified) llvm/include/llvm/Transforms/Utils/SSAUpdaterBulk.h (+24)
  • (modified) llvm/lib/Transforms/Utils/SSAUpdaterBulk.cpp (+47)
diff --git a/llvm/include/llvm/Transforms/Utils/SSAUpdaterBulk.h b/llvm/include/llvm/Transforms/Utils/SSAUpdaterBulk.h
index ad24cb454d5e7..6894e4fa0a357 100644
--- a/llvm/include/llvm/Transforms/Utils/SSAUpdaterBulk.h
+++ b/llvm/include/llvm/Transforms/Utils/SSAUpdaterBulk.h
@@ -79,6 +79,30 @@ class SSAUpdaterBulk {
   /// vector.
   void RewriteAllUses(DominatorTree *DT,
                       SmallVectorImpl<PHINode *> *InsertedPHIs = nullptr);
+
+  /// Rewrite all uses and simplify the inserted PHI nodes.
+  /// Use this method to preserve behavior when replacing SSAUpdater.
+  void RewriteAndSimplifyAllUses(DominatorTree *DT) {
+    SmallVector<PHINode *, 8> NewPHIs;
+    RewriteAllUses(DT, &NewPHIs);
+    simplifyPass(NewPHIs);
+  }
+
+  /// Same as previous, but return inserted PHI nodes in InsertedPHIs.
+  void RewriteAndSimplifyAllUses(DominatorTree *DT,
+                                 SmallVectorImpl<PHINode *> &InsertedPHIs) {
+    RewriteAllUses(DT, &InsertedPHIs);
+    simplifyPass(InsertedPHIs);
+    // Remove nullptrs from the worklist
+    InsertedPHIs.erase(
+        std::remove(InsertedPHIs.begin(), InsertedPHIs.end(), nullptr),
+        InsertedPHIs.end());
+  }
+
+private:
+  /// Perform a single pass of simplification over the worklist of PHIs.
+  /// Pointers to replaced phis are nulled out.
+  static bool simplifyPass(SmallVectorImpl<PHINode *> &Worklist);
 };
 
 } // end namespace llvm
diff --git a/llvm/lib/Transforms/Utils/SSAUpdaterBulk.cpp b/llvm/lib/Transforms/Utils/SSAUpdaterBulk.cpp
index cad7ff64c01fb..dac964b092ec5 100644
--- a/llvm/lib/Transforms/Utils/SSAUpdaterBulk.cpp
+++ b/llvm/lib/Transforms/Utils/SSAUpdaterBulk.cpp
@@ -11,6 +11,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/Transforms/Utils/SSAUpdaterBulk.h"
+#include "llvm/Analysis/InstructionSimplify.h"
 #include "llvm/Analysis/IteratedDominanceFrontier.h"
 #include "llvm/IR/BasicBlock.h"
 #include "llvm/IR/Dominators.h"
@@ -182,3 +183,49 @@ void SSAUpdaterBulk::RewriteAllUses(DominatorTree *DT,
     }
   }
 }
+
+static bool isEquivalentPHI(PHINode *PHI1, PHINode *PHI2) {
+  if (PHI1->getNumIncomingValues() != PHI2->getNumIncomingValues())
+    return false;
+
+  unsigned I = 0, NumValues = PHI1->getNumIncomingValues();
+  for (; I != NumValues; ++I) {
+    if (PHI1->getIncomingBlock(I) != PHI2->getIncomingBlock(I))
+      break;
+    if (PHI1->getIncomingValue(I) != PHI2->getIncomingValue(I))
+      return false;
+  }
+  // TODO: add procesing if phis have different order of incoming values.
+  return I == NumValues;
+}
+
+bool SSAUpdaterBulk::simplifyPass(SmallVectorImpl<PHINode *> &Worklist) {
+  if (Worklist.empty())
+    return false;
+
+  auto findEquivalentPHI = [](PHINode *PHI) -> Value * {
+    BasicBlock *BB = PHI->getParent();
+    for (auto &OtherPHI : BB->phis()) {
+      if (PHI != &OtherPHI && isEquivalentPHI(PHI, &OtherPHI)) {
+        return &OtherPHI;
+      }
+    }
+    return nullptr;
+  };
+
+  const DataLayout &DL = Worklist.front()->getParent()->getDataLayout();
+  bool Change = false;
+  for (PHINode *&PHI : Worklist) {
+    Value *Replacement = simplifyInstruction(PHI, DL);
+    if (!Replacement)
+      Replacement = findEquivalentPHI(PHI);
+
+    if (Replacement) {
+      PHI->replaceAllUsesWith(Replacement);
+      PHI->eraseFromParent();
+      PHI = nullptr; // Mark as removed
+      Change = true;
+    }
+  }
+  return Change;
+}

@vpykhtin vpykhtin requested a review from arsenm March 19, 2025 11:09
@vpykhtin vpykhtin requested a review from nikic March 19, 2025 17:52
}
}
return nullptr;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just use EliminateDuplicatePHINodes here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The EliminateDuplicatePHINodes function has two main issues. First, it replaces later PHI nodes with earlier ones, leading to unexpected behavior where existing PHI nodes are replaced with new ones. Second, it tests all PHI nodes instead of focusing solely on the newly added ones, which would be more efficient.

@nikic
Copy link
Contributor

nikic commented Mar 19, 2025

I opted to implement phi optimization as a separate step to maintain the current behavior of SSAUpdaterBulk::RewriteAllUses while ensuring the implementation remains straightforward.

Do we have some uses that don't want the simplification behavior? If not, I'd rather change the behavior of the existing methods.

@vpykhtin
Copy link
Contributor Author

vpykhtin commented Mar 20, 2025

Do we have some uses that don't want the simplification behavior? If not, I'd rather change the behavior of the existing methods.

I don't know, it's very likely that everybody would prefer optimizing version.

In this case it would be easier to add EliminateDuplicatePHINodes to the end of RewriteAllUses as I need to collect the set of BB to process.

@vpykhtin
Copy link
Contributor Author

The EliminateDuplicatePHINodes function has two main issues. First, it replaces later PHI nodes with earlier ones, leading to unexpected behavior where existing PHI nodes are replaced with new ones. Second, it tests all PHI nodes instead of focusing solely on the newly added ones, which would be more efficient.

Copy link

github-actions bot commented Mar 21, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@vpykhtin
Copy link
Contributor Author

I peeked into EliminateDuplicatePHINodes and used isIdenticalToWhenDefined instead of new code.

@vpykhtin
Copy link
Contributor Author

Hi @nikic, I believe this is ready for submission in its current state. I chose to keep this code as a separate 'pass' rather than integrating it directly into RewriteAllUses for two reasons:

  1. Identifying an equivalent PHI in RewriteAllUses and performing the subsequent RAUW would require using TrackingVH in BBInfo.

  2. We can rename RewriteAndSimplifyAllUses to RewriteAllUses in a separate patch.

@vpykhtin
Copy link
Contributor Author

Used EliminateDuplicatePHINodes because it's much faster.

Turned on optimizations by default, got some failures but they're mostly reorder of phi's.

@vpykhtin vpykhtin requested a review from nikic March 25, 2025 16:50
@vpykhtin
Copy link
Contributor Author

Current implementation is incorrect: I cannot move new phis to the end because they can be used by existing phis if the user added 'variable' as incoming value for an existed phi.

@nikic
Copy link
Contributor

nikic commented Apr 4, 2025

Current implementation is incorrect: I cannot move new phis to the end because they can be used by existing phis if the user added 'variable' as incoming value for an existed phi.

Maybe I'm misunderstanding what you mean here, but phi's are all evaluated at the same time, not in order. If a phi refers to another phi, it always refers to the value of the phi at a previous cycle iteration.

@vpykhtin
Copy link
Contributor Author

vpykhtin commented Apr 6, 2025

Maybe I'm misunderstanding what you mean here, but phi's are all evaluated at the same time, not in order. If a phi refers to another phi, it always refers to the value of the phi at a previous cycle iteration.

Sorry, false alarm, otherwise phis wouldn't be able to reference itself. I'm preparing new patch as a stacked graphite PR that would include this patch and its' use in AMDGPU backend.

return false;

const DataLayout &DL = Worklist.front()->getParent()->getDataLayout();
bool Change = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
bool Change = false;
bool Changed = false;

nit: More typical naming for this flag.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will address this in the new stacker PR.

if (Value *Replacement = simplifyInstruction(PHI, DL)) {
PHI->replaceAllUsesWith(Replacement);
PHI->eraseFromParent();
PHI = nullptr; // Mark as removed
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also iterate over Worklist using erase_if instead to avoid leaving behind nullptrs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, but I don't see much sense since this is internal routine and we're know what we're doing after that :)

@vpykhtin
Copy link
Contributor Author

Hi @nikic, sorry for the mess, I've replaced this PR with new stacked version, #135180, I had to start it someday.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants