[llvm] [GVN] Move `GVNPass` and `GVNLeaderMap` out of `GVN.h` and into 'GVN.cpp` (NFC) (PR #226234)

Momchil Velikov via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 30 02:58:39 PDT 2026


https://github.com/momchil-velikov updated https://github.com/llvm/llvm-project/pull/226234

>From b9a4527b652a3abb2ec7fe1aeac76901e784114e Mon Sep 17 00:00:00 2001
From: Momchil Velikov <momchil.velikov at arm.com>
Date: Thu, 24 Sep 2026 13:41:53 +0100
Subject: [PATCH 1/3] [GVN] Move `GVNPass` and `GVNLeaderMap` out of `GVN.h`
 and into 'GVN.cpp` (NFC)

Rename `GVNPass` to `GVNPassImpl`. Leave in the header only
the class for interfacing with the pass manager.
---
 llvm/include/llvm/Transforms/Scalar/GVN.h | 335 ---------------
 llvm/lib/Transforms/Scalar/GVN.cpp        | 487 ++++++++++++++++++----
 2 files changed, 402 insertions(+), 420 deletions(-)

diff --git a/llvm/include/llvm/Transforms/Scalar/GVN.h b/llvm/include/llvm/Transforms/Scalar/GVN.h
index c59d830bb7016..5e8eb941e4670 100644
--- a/llvm/include/llvm/Transforms/Scalar/GVN.h
+++ b/llvm/include/llvm/Transforms/Scalar/GVN.h
@@ -15,54 +15,12 @@
 #ifndef LLVM_TRANSFORMS_SCALAR_GVN_H
 #define LLVM_TRANSFORMS_SCALAR_GVN_H
 
-#include "llvm/ADT/DenseMap.h"
-#include "llvm/ADT/MapVector.h"
-#include "llvm/ADT/SetVector.h"
-#include "llvm/ADT/SmallVector.h"
-#include "llvm/Analysis/PHITransAddr.h"
-#include "llvm/IR/Dominators.h"
-#include "llvm/IR/InstrTypes.h"
 #include "llvm/IR/PassManager.h"
-#include "llvm/IR/ValueHandle.h"
-#include "llvm/Support/Allocator.h"
-#include "llvm/Support/Compiler.h"
-#include "llvm/Transforms/Scalar/GVNValueTable.h"
-#include <cstdint>
 #include <optional>
-#include <utility>
-#include <variant>
-#include <vector>
 
 namespace llvm {
 
-class AAResults;
-class AssumeInst;
-class AssumptionCache;
-class BasicBlock;
-class BatchAAResults;
-class CallInst;
-class CondBrInst;
-class EarliestEscapeAnalysis;
-class ExtractValueInst;
-class Function;
 class FunctionPass;
-class GVNLegacyPass;
-class GetElementPtrInst;
-class ImplicitControlFlowTracking;
-class LoadInst;
-class LoopInfo;
-class MemDepResult;
-class MemoryAccess;
-class MemoryDependenceResults;
-class MemoryLocation;
-class MemorySSA;
-class MemorySSAUpdater;
-class NonLocalDepResult;
-class OptimizationRemarkEmitter;
-class PHINode;
-class TargetLibraryInfo;
-class Value;
-class IntrinsicInst;
 
 /// A set of parameters to control various transforms performed by GVN pass.
 //  Each of the optional boolean parameters can be set to:
@@ -117,96 +75,10 @@ struct GVNOptions {
   }
 };
 
-/// A mapping from value numbers to lists of Value*'s that
-/// have that value number. Use getLeaders to query it.
-class GVNLeaderMap {
-public:
-  struct LeaderTableEntry {
-    // Use AssertingVH here to catch dangling Value*'s in the leader table.
-    // Will crash if the value gets deleted before the AssertingVH is
-    // destroyed.
-    AssertingVH<Value> Val;
-    const BasicBlock *BB;
-    LeaderTableEntry(Value *V, const BasicBlock *BB) : Val(V), BB(BB) {}
-  };
-
-private:
-  struct LeaderListNode {
-    LeaderTableEntry Entry;
-    LeaderListNode *Next;
-    LeaderListNode(Value *V, const BasicBlock *BB, LeaderListNode *Next)
-        : Entry(V, BB), Next(Next) {}
-  };
-  DenseMap<uint32_t, LeaderListNode> NumToLeaders;
-  BumpPtrAllocator TableAllocator;
-
-public:
-  class leader_iterator {
-    const LeaderListNode *Current;
-
-  public:
-    using iterator_category = std::forward_iterator_tag;
-    using value_type = const LeaderTableEntry;
-    using difference_type = std::ptrdiff_t;
-    using pointer = value_type *;
-    using reference = value_type &;
-
-    leader_iterator(const LeaderListNode *C) : Current(C) {}
-    leader_iterator &operator++() {
-      assert(Current && "Dereferenced end of leader list!");
-      Current = Current->Next;
-      return *this;
-    }
-    bool operator==(const leader_iterator &Other) const {
-      return Current == Other.Current;
-    }
-    bool operator!=(const leader_iterator &Other) const {
-      return Current != Other.Current;
-    }
-    reference operator*() const { return Current->Entry; }
-  };
-
-  iterator_range<leader_iterator> getLeaders(uint32_t N) {
-    auto I = NumToLeaders.find(N);
-    if (I == NumToLeaders.end()) {
-      return iterator_range(leader_iterator(nullptr), leader_iterator(nullptr));
-    }
-
-    return iterator_range(leader_iterator(&I->second),
-                          leader_iterator(nullptr));
-  }
-
-  LLVM_ABI void insert(uint32_t N, Value *V, const BasicBlock *BB);
-  LLVM_ABI void erase(uint32_t N, Instruction *I, const BasicBlock *BB);
-  void clear() {
-    // Manually destroy non-head nodes (in BumpPtrAllocator) to properly
-    // clean up AssertingVH handles before Reset(). Head nodes are destroyed
-    // by NumToLeaders.clear() below.
-    for (auto &[_, HeadNode] : NumToLeaders) {
-      LeaderListNode *N = HeadNode.Next;
-      while (N) {
-        auto *Next = N->Next;
-        N->~LeaderListNode();
-        N = Next;
-      }
-    }
-    NumToLeaders.clear();
-    TableAllocator.Reset();
-  }
-};
-
-/// The core GVN pass object.
-///
-/// FIXME: We should have a good summary of the GVN algorithm implemented by
-/// this particular pass here.
 class GVNPass : public OptionalPassInfoMixin<GVNPass> {
   GVNOptions Options;
 
 public:
-  struct Expression;
-  struct AvailableValue;
-  struct AvailableValueInBlock;
-
   GVNPass(GVNOptions Options = {}) : Options(Options) {}
 
   /// Run the pass over the function.
@@ -215,213 +87,6 @@ class GVNPass : public OptionalPassInfoMixin<GVNPass> {
   LLVM_ABI void
   printPipeline(raw_ostream &OS,
                 function_ref<StringRef(StringRef)> MapClassName2PassName);
-
-  /// This removes the specified instruction from
-  /// our various maps and marks it for deletion.
-  LLVM_ABI void salvageAndRemoveInstruction(Instruction *I);
-
-  DominatorTree &getDominatorTree() const { return *DT; }
-  AAResults *getAliasAnalysis() const { return VN.getAliasAnalysis(); }
-  MemoryDependenceResults &getMemDep() const { return *MD; }
-
-  LLVM_ABI bool isScalarPREEnabled() const;
-  LLVM_ABI bool isLoadPREEnabled() const;
-  LLVM_ABI bool isLoadInLoopPREEnabled() const;
-  LLVM_ABI bool isLoadPRESplitBackedgeEnabled() const;
-  LLVM_ABI bool isMemDepEnabled() const;
-  LLVM_ABI bool isMemorySSAEnabled() const;
-
-private:
-  friend class GVNLegacyPass;
-  friend struct DenseMapInfo<Expression>;
-
-  MemoryDependenceResults *MD = nullptr;
-  DominatorTree *DT = nullptr;
-  const TargetLibraryInfo *TLI = nullptr;
-  AssumptionCache *AC = nullptr;
-  SetVector<BasicBlock *> DeadBlocks;
-  OptimizationRemarkEmitter *ORE = nullptr;
-  ImplicitControlFlowTracking *ICF = nullptr;
-  LoopInfo *LI = nullptr;
-  AAResults *AA = nullptr;
-  MemorySSAUpdater *MSSAU = nullptr;
-
-  GVNValueTable VN;
-
-  GVNLeaderMap LeaderTable;
-
-  // Map the block to reversed postorder traversal number. It is used to
-  // find back edge easily.
-  DenseMap<AssertingVH<BasicBlock>, uint32_t> BlockRPONumber;
-
-  // This is set 'true' initially and also when new blocks have been added to
-  // the function being analyzed. This boolean is used to control the updating
-  // of BlockRPONumber prior to accessing the contents of BlockRPONumber.
-  bool InvalidBlockRPONumbers = true;
-
-  using LoadDepVect = SmallVector<NonLocalDepResult, 64>;
-  using AvailValInBlkVect = SmallVector<AvailableValueInBlock, 64>;
-  using UnavailBlkVect = SmallVector<BasicBlock *, 64>;
-
-  bool runImpl(Function &F, AssumptionCache &RunAC, DominatorTree &RunDT,
-               const TargetLibraryInfo &RunTLI, AAResults &RunAA,
-               MemoryDependenceResults *RunMD, LoopInfo &LI,
-               OptimizationRemarkEmitter *ORE, MemorySSA *MSSA = nullptr);
-
-  // List of critical edges to be split between iterations.
-  SmallVector<std::pair<Instruction *, unsigned>, 4> ToSplit;
-
-  enum class DepKind {
-    Other = 0, // Unknown value.
-    Def,       // Exactly overlapping locations.
-    Clobber,   // Reaching value superset of needed bits.
-    Select,    // Reaching value is a select of two reaching addresses.
-  };
-
-  // Describe a memory location value, such that there exists a path to a point
-  // in the program, along which that memory location is not modified.
-  struct ReachingMemVal {
-    DepKind Kind;
-    BasicBlock *Block;
-    const Value *Addr;
-    Instruction *Inst;
-    int32_t Offset;
-    // For DepKind::Select only: the condition and the two addresses referenced
-    // by the "true" and "false" side of the select-dependent load.
-    const Value *SelCond = nullptr;
-    const Value *SelTrueAddr = nullptr;
-    const Value *SelFalseAddr = nullptr;
-
-    static ReachingMemVal getUnknown(BasicBlock *BB, const Value *Addr,
-                                     Instruction *Inst = nullptr) {
-      return {DepKind::Other, BB, Addr, Inst, -1};
-    }
-
-    static ReachingMemVal getDef(const Value *Addr, Instruction *Inst) {
-      return {DepKind::Def, Inst->getParent(), Addr, Inst, -1};
-    }
-
-    static ReachingMemVal getClobber(const Value *Addr, Instruction *Inst,
-                                     int32_t Offset = -1) {
-      return {DepKind::Clobber, Inst->getParent(), Addr, Inst, Offset};
-    }
-
-    static ReachingMemVal getSelect(BasicBlock *BB, const Value *Cond,
-                                    const Value *TrueAddr,
-                                    const Value *FalseAddr) {
-      return {DepKind::Select, BB,       nullptr, nullptr, -1, Cond,
-              TrueAddr,        FalseAddr};
-    }
-  };
-
-  struct DependencyBlockInfo {
-    DependencyBlockInfo() = delete;
-    DependencyBlockInfo(const PHITransAddr &Addr, MemoryAccess *ClobberMA)
-        : Addr(Addr), InitialClobberMA(ClobberMA), ClobberMA(ClobberMA),
-          ForceUnknown(false), Visited(false) {}
-    PHITransAddr Addr;
-    MemoryAccess *InitialClobberMA;
-    MemoryAccess *ClobberMA;
-    std::optional<ReachingMemVal> MemVal;
-    bool ForceUnknown : 1;
-    bool Visited : 1;
-  };
-
-  using DependencyBlockSet = DenseMap<BasicBlock *, DependencyBlockInfo>;
-
-  std::optional<GVNPass::ReachingMemVal> scanMemoryAccessesUsers(
-      const MemoryLocation &Loc, bool IsInvariantLoad, BasicBlock *BB,
-      const SmallVectorImpl<MemoryAccess *> &ClobbersList, MemorySSA &MSSA,
-      BatchAAResults &AA, LoadInst *L = nullptr);
-
-  std::optional<GVNPass::ReachingMemVal>
-  accessMayModifyLocation(MemoryAccess *ClobberMA, const MemoryLocation &Loc,
-                          Align LoadAlign, bool IsInvariantLoad, BasicBlock *BB,
-                          MemorySSA &MSSA, BatchAAResults &AA);
-
-  bool collectPredecessors(BasicBlock *BB, const PHITransAddr &Addr,
-                           MemoryAccess *ClobberMA, DependencyBlockSet &Blocks,
-                           SmallVectorImpl<BasicBlock *> &Worklist);
-
-  void collectClobberList(SmallVectorImpl<MemoryAccess *> &Clobbers,
-                          BasicBlock *BB, const DependencyBlockInfo &StartInfo,
-                          const DependencyBlockSet &Blocks, MemorySSA &MSSA);
-
-  bool findReachingValuesForLoad(LoadInst *Inst,
-                                 SmallVectorImpl<ReachingMemVal> &Values,
-                                 MemorySSA &MSSA, AAResults &AA);
-
-  // Helper functions of redundant load elimination.
-  bool processLoad(LoadInst *L);
-  bool processMaskedLoad(IntrinsicInst *I);
-  bool processNonLocalLoad(LoadInst *L);
-  bool processNonLocalLoad(LoadInst *L, SmallVectorImpl<ReachingMemVal> &Deps);
-  bool processAssumeIntrinsic(AssumeInst *II);
-
-  /// Given a local dependency (Def or Clobber) determine if a value is
-  /// available for the load.
-  std::optional<AvailableValue>
-  analyzeLoadAvailability(LoadInst *Load, const ReachingMemVal &Dep,
-                          Value *Address);
-
-  /// Given a select-dependency for the load (the load address is a select of
-  /// \p TrueAddr and \p FalseAddr guarded by \p Cond), determine whether a
-  /// value is available by finding dominating values for both addresses.  If
-  /// so, the load can be rematerialized as a select of those two values.
-  std::optional<AvailableValue>
-  analyzeSelectAvailability(LoadInst *Load, Value *Cond, Value *TrueAddr,
-                            Value *FalseAddr, Instruction *From);
-
-  /// Given a list of non-local dependencies, determine if a value is
-  /// available for the load in each specified block.  If it is, add it to
-  /// ValuesPerBlock.  If not, add it to UnavailableBlocks.
-  void analyzeLoadAvailability(LoadInst *Load,
-                               SmallVectorImpl<ReachingMemVal> &Deps,
-                               AvailValInBlkVect &ValuesPerBlock,
-                               UnavailBlkVect &UnavailableBlocks);
-
-  /// Given a critical edge from Pred to LoadBB, find a load instruction
-  /// which is identical to Load from another successor of Pred.
-  LoadInst *findLoadToHoistIntoPred(BasicBlock *Pred, BasicBlock *LoadBB,
-                                    LoadInst *Load);
-
-  bool performLoadPRE(LoadInst *Load, AvailValInBlkVect &ValuesPerBlock,
-                      UnavailBlkVect &UnavailableBlocks);
-
-  /// Try to replace a load which executes on each loop iteraiton with Phi
-  /// translation of load in preheader and load(s) in conditionally executed
-  /// paths.
-  bool performLoopLoadPRE(LoadInst *Load, AvailValInBlkVect &ValuesPerBlock,
-                          UnavailBlkVect &UnavailableBlocks);
-
-  /// Eliminates partially redundant \p Load, replacing it with \p
-  /// AvailableLoads (connected by Phis if needed).
-  void eliminatePartiallyRedundantLoad(
-      LoadInst *Load, AvailValInBlkVect &ValuesPerBlock,
-      MapVector<BasicBlock *, Value *> &AvailableLoads,
-      MapVector<BasicBlock *, LoadInst *> *CriticalEdgePredAndLoad);
-
-  // Other helper routines.
-  bool processInstruction(Instruction *I);
-  bool processBlock(BasicBlock *BB);
-  bool iterateOnFunction(Function &F);
-  bool performPRE(Function &F);
-  bool performScalarPRE(Instruction *I);
-  bool performScalarPREInsertion(Instruction *Instr, BasicBlock *Pred,
-                                 BasicBlock *Curr, unsigned int ValNo);
-  Value *findLeader(const BasicBlock *BB, uint32_t Num);
-  void cleanupGlobalSets();
-  void removeInstruction(Instruction *I);
-  void verifyRemoved(const Instruction *I) const;
-  bool splitCriticalEdges();
-  BasicBlock *splitCriticalEdges(BasicBlock *Pred, BasicBlock *Succ);
-  bool
-  propagateEquality(Value *LHS, Value *RHS,
-                    const std::variant<BasicBlockEdge, Instruction *> &Root);
-  bool processFoldableCondBr(CondBrInst *BI);
-  void addDeadBlock(BasicBlock *BB);
-  void assignValNumForDeadCode();
-  void assignBlockRPONumber(Function &F);
 };
 
 /// Create a legacy GVN pass.
diff --git a/llvm/lib/Transforms/Scalar/GVN.cpp b/llvm/lib/Transforms/Scalar/GVN.cpp
index 463520f2b32d7..154e3106a39d3 100644
--- a/llvm/lib/Transforms/Scalar/GVN.cpp
+++ b/llvm/lib/Transforms/Scalar/GVN.cpp
@@ -70,6 +70,7 @@
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/raw_ostream.h"
+#include "llvm/Transforms/Scalar/GVNValueTable.h"
 #include "llvm/Transforms/Utils/AssumeBundleBuilder.h"
 #include "llvm/Transforms/Utils/BasicBlockUtils.h"
 #include "llvm/Transforms/Utils/Local.h"
@@ -80,14 +81,12 @@
 #include <cstdint>
 #include <optional>
 #include <utility>
+#include <variant>
 
 using namespace llvm;
 using namespace llvm::VNCoercion;
 using namespace PatternMatch;
 
-using AvailableValue = GVNPass::AvailableValue;
-using AvailableValueInBlock = GVNPass::AvailableValueInBlock;
-
 #define DEBUG_TYPE "gvn"
 
 STATISTIC(NumGVNInstr, "Number of instructions deleted");
@@ -197,11 +196,319 @@ template <> struct llvm::DenseMapInfo<GVNValueTable::Expression> {
   }
 };
 
+/// A mapping from value numbers to lists of Value*'s that
+/// have that value number. Use getLeaders to query it.
+class llvm::GVNLeaderMap {
+public:
+  struct LeaderTableEntry {
+    // Use AssertingVH here to catch dangling Value*'s in the leader table.
+    // Will crash if the value gets deleted before the AssertingVH is
+    // destroyed.
+    AssertingVH<Value> Val;
+    const BasicBlock *BB;
+    LeaderTableEntry(Value *V, const BasicBlock *BB) : Val(V), BB(BB) {}
+  };
+
+private:
+  struct LeaderListNode {
+    LeaderTableEntry Entry;
+    LeaderListNode *Next;
+    LeaderListNode(Value *V, const BasicBlock *BB, LeaderListNode *Next)
+        : Entry(V, BB), Next(Next) {}
+  };
+  DenseMap<uint32_t, LeaderListNode> NumToLeaders;
+  BumpPtrAllocator TableAllocator;
+
+public:
+  class leader_iterator {
+    const LeaderListNode *Current;
+
+  public:
+    using iterator_category = std::forward_iterator_tag;
+    using value_type = const LeaderTableEntry;
+    using difference_type = std::ptrdiff_t;
+    using pointer = value_type *;
+    using reference = value_type &;
+
+    leader_iterator(const LeaderListNode *C) : Current(C) {}
+    leader_iterator &operator++() {
+      assert(Current && "Dereferenced end of leader list!");
+      Current = Current->Next;
+      return *this;
+    }
+    bool operator==(const leader_iterator &Other) const {
+      return Current == Other.Current;
+    }
+    bool operator!=(const leader_iterator &Other) const {
+      return Current != Other.Current;
+    }
+    reference operator*() const { return Current->Entry; }
+  };
+
+  iterator_range<leader_iterator> getLeaders(uint32_t N) {
+    auto I = NumToLeaders.find(N);
+    if (I == NumToLeaders.end()) {
+      return iterator_range(leader_iterator(nullptr), leader_iterator(nullptr));
+    }
+
+    return iterator_range(leader_iterator(&I->second),
+                          leader_iterator(nullptr));
+  }
+
+  LLVM_ABI void insert(uint32_t N, Value *V, const BasicBlock *BB);
+  LLVM_ABI void erase(uint32_t N, Instruction *I, const BasicBlock *BB);
+  void clear() {
+    // Manually destroy non-head nodes (in BumpPtrAllocator) to properly
+    // clean up AssertingVH handles before Reset(). Head nodes are destroyed
+    // by NumToLeaders.clear() below.
+    for (auto &[_, HeadNode] : NumToLeaders) {
+      LeaderListNode *N = HeadNode.Next;
+      while (N) {
+        auto *Next = N->Next;
+        N->~LeaderListNode();
+        N = Next;
+      }
+    }
+    NumToLeaders.clear();
+    TableAllocator.Reset();
+  }
+};
+
+/// The core GVN pass object.
+///
+/// FIXME: We should have a good summary of the GVN algorithm implemented by
+/// this particular pass here.
+class GVNPassImpl {
+  llvm::GVNOptions Options;
+
+public:
+  struct Expression;
+  struct AvailableValue;
+  struct AvailableValueInBlock;
+
+  GVNPassImpl(llvm::GVNOptions Options = {}) : Options(Options) {}
+
+  /// Run the pass over the function.
+  LLVM_ABI PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
+
+  LLVM_ABI void
+  printPipeline(raw_ostream &OS,
+                function_ref<StringRef(StringRef)> MapClassName2PassName);
+
+  /// This removes the specified instruction from
+  /// our various maps and marks it for deletion.
+  LLVM_ABI void salvageAndRemoveInstruction(Instruction *I);
+
+  DominatorTree &getDominatorTree() const { return *DT; }
+  AAResults *getAliasAnalysis() const { return VN.getAliasAnalysis(); }
+  MemoryDependenceResults &getMemDep() const { return *MD; }
+
+  LLVM_ABI bool isScalarPREEnabled() const;
+  LLVM_ABI bool isLoadPREEnabled() const;
+  LLVM_ABI bool isLoadInLoopPREEnabled() const;
+  LLVM_ABI bool isLoadPRESplitBackedgeEnabled() const;
+  LLVM_ABI bool isMemDepEnabled() const;
+  LLVM_ABI bool isMemorySSAEnabled() const;
+
+private:
+  friend class llvm::GVNPass;
+  friend class GVNLegacyPass;
+  friend struct DenseMapInfo<Expression>;
+
+  MemoryDependenceResults *MD = nullptr;
+  DominatorTree *DT = nullptr;
+  const TargetLibraryInfo *TLI = nullptr;
+  AssumptionCache *AC = nullptr;
+  SetVector<BasicBlock *> DeadBlocks;
+  OptimizationRemarkEmitter *ORE = nullptr;
+  ImplicitControlFlowTracking *ICF = nullptr;
+  LoopInfo *LI = nullptr;
+  AAResults *AA = nullptr;
+  MemorySSAUpdater *MSSAU = nullptr;
+
+  GVNValueTable VN;
+
+  GVNLeaderMap LeaderTable;
+
+  // Map the block to reversed postorder traversal number. It is used to
+  // find back edge easily.
+  DenseMap<AssertingVH<BasicBlock>, uint32_t> BlockRPONumber;
+
+  // This is set 'true' initially and also when new blocks have been added to
+  // the function being analyzed. This boolean is used to control the updating
+  // of BlockRPONumber prior to accessing the contents of BlockRPONumber.
+  bool InvalidBlockRPONumbers = true;
+
+  using LoadDepVect = SmallVector<NonLocalDepResult, 64>;
+  using AvailValInBlkVect = SmallVector<AvailableValueInBlock, 64>;
+  using UnavailBlkVect = SmallVector<BasicBlock *, 64>;
+
+  bool run(Function &F, AssumptionCache &RunAC, DominatorTree &RunDT,
+           const TargetLibraryInfo &RunTLI, AAResults &RunAA,
+           MemoryDependenceResults *RunMD, LoopInfo &LI,
+           OptimizationRemarkEmitter *ORE, MemorySSA *MSSA = nullptr);
+
+  // List of critical edges to be split between iterations.
+  SmallVector<std::pair<Instruction *, unsigned>, 4> ToSplit;
+
+  enum class DepKind {
+    Other = 0, // Unknown value.
+    Def,       // Exactly overlapping locations.
+    Clobber,   // Reaching value superset of needed bits.
+    Select,    // Reaching value is a select of two reaching addresses.
+  };
+
+  // Describe a memory location value, such that there exists a path to a point
+  // in the program, along which that memory location is not modified.
+  struct ReachingMemVal {
+    DepKind Kind;
+    BasicBlock *Block;
+    const Value *Addr;
+    Instruction *Inst;
+    int32_t Offset;
+    // For DepKind::Select only: the condition and the two addresses referenced
+    // by the "true" and "false" side of the select-dependent load.
+    const Value *SelCond = nullptr;
+    const Value *SelTrueAddr = nullptr;
+    const Value *SelFalseAddr = nullptr;
+
+    static ReachingMemVal getUnknown(BasicBlock *BB, const Value *Addr,
+                                     Instruction *Inst = nullptr) {
+      return {DepKind::Other, BB, Addr, Inst, -1};
+    }
+
+    static ReachingMemVal getDef(const Value *Addr, Instruction *Inst) {
+      return {DepKind::Def, Inst->getParent(), Addr, Inst, -1};
+    }
+
+    static ReachingMemVal getClobber(const Value *Addr, Instruction *Inst,
+                                     int32_t Offset = -1) {
+      return {DepKind::Clobber, Inst->getParent(), Addr, Inst, Offset};
+    }
+
+    static ReachingMemVal getSelect(BasicBlock *BB, const Value *Cond,
+                                    const Value *TrueAddr,
+                                    const Value *FalseAddr) {
+      return {DepKind::Select, BB,       nullptr, nullptr, -1, Cond,
+              TrueAddr,        FalseAddr};
+    }
+  };
+
+  struct DependencyBlockInfo {
+    DependencyBlockInfo() = delete;
+    DependencyBlockInfo(const PHITransAddr &Addr, MemoryAccess *ClobberMA)
+        : Addr(Addr), InitialClobberMA(ClobberMA), ClobberMA(ClobberMA),
+          ForceUnknown(false), Visited(false) {}
+    PHITransAddr Addr;
+    MemoryAccess *InitialClobberMA;
+    MemoryAccess *ClobberMA;
+    std::optional<ReachingMemVal> MemVal;
+    bool ForceUnknown : 1;
+    bool Visited : 1;
+  };
+
+  using DependencyBlockSet = DenseMap<BasicBlock *, DependencyBlockInfo>;
+
+  std::optional<GVNPassImpl::ReachingMemVal> scanMemoryAccessesUsers(
+      const MemoryLocation &Loc, bool IsInvariantLoad, BasicBlock *BB,
+      const SmallVectorImpl<MemoryAccess *> &ClobbersList, MemorySSA &MSSA,
+      BatchAAResults &AA, LoadInst *L = nullptr);
+
+  std::optional<GVNPassImpl::ReachingMemVal>
+  accessMayModifyLocation(MemoryAccess *ClobberMA, const MemoryLocation &Loc,
+                          Align LoadAlign, bool IsInvariantLoad, BasicBlock *BB,
+                          MemorySSA &MSSA, BatchAAResults &AA);
+
+  bool collectPredecessors(BasicBlock *BB, const PHITransAddr &Addr,
+                           MemoryAccess *ClobberMA, DependencyBlockSet &Blocks,
+                           SmallVectorImpl<BasicBlock *> &Worklist);
+
+  void collectClobberList(SmallVectorImpl<MemoryAccess *> &Clobbers,
+                          BasicBlock *BB, const DependencyBlockInfo &StartInfo,
+                          const DependencyBlockSet &Blocks, MemorySSA &MSSA);
+
+  bool findReachingValuesForLoad(LoadInst *Inst,
+                                 SmallVectorImpl<ReachingMemVal> &Values,
+                                 MemorySSA &MSSA, AAResults &AA);
+
+  // Helper functions of redundant load elimination.
+  bool processLoad(LoadInst *L);
+  bool processMaskedLoad(IntrinsicInst *I);
+  bool processNonLocalLoad(LoadInst *L);
+  bool processNonLocalLoad(LoadInst *L, SmallVectorImpl<ReachingMemVal> &Deps);
+  bool processAssumeIntrinsic(AssumeInst *II);
+
+  /// Given a local dependency (Def or Clobber) determine if a value is
+  /// available for the load.
+  std::optional<AvailableValue>
+  analyzeLoadAvailability(LoadInst *Load, const ReachingMemVal &Dep,
+                          Value *Address);
+
+  /// Given a select-dependency for the load (the load address is a select of
+  /// \p TrueAddr and \p FalseAddr guarded by \p Cond), determine whether a
+  /// value is available by finding dominating values for both addresses.  If
+  /// so, the load can be rematerialized as a select of those two values.
+  std::optional<AvailableValue>
+  analyzeSelectAvailability(LoadInst *Load, Value *Cond, Value *TrueAddr,
+                            Value *FalseAddr, Instruction *From);
+
+  /// Given a list of non-local dependencies, determine if a value is
+  /// available for the load in each specified block.  If it is, add it to
+  /// ValuesPerBlock.  If not, add it to UnavailableBlocks.
+  void analyzeLoadAvailability(LoadInst *Load,
+                               SmallVectorImpl<ReachingMemVal> &Deps,
+                               AvailValInBlkVect &ValuesPerBlock,
+                               UnavailBlkVect &UnavailableBlocks);
+
+  /// Given a critical edge from Pred to LoadBB, find a load instruction
+  /// which is identical to Load from another successor of Pred.
+  LoadInst *findLoadToHoistIntoPred(BasicBlock *Pred, BasicBlock *LoadBB,
+                                    LoadInst *Load);
+
+  bool performLoadPRE(LoadInst *Load, AvailValInBlkVect &ValuesPerBlock,
+                      UnavailBlkVect &UnavailableBlocks);
+
+  /// Try to replace a load which executes on each loop iteraiton with Phi
+  /// translation of load in preheader and load(s) in conditionally executed
+  /// paths.
+  bool performLoopLoadPRE(LoadInst *Load, AvailValInBlkVect &ValuesPerBlock,
+                          UnavailBlkVect &UnavailableBlocks);
+
+  /// Eliminates partially redundant \p Load, replacing it with \p
+  /// AvailableLoads (connected by Phis if needed).
+  void eliminatePartiallyRedundantLoad(
+      LoadInst *Load, AvailValInBlkVect &ValuesPerBlock,
+      MapVector<BasicBlock *, Value *> &AvailableLoads,
+      MapVector<BasicBlock *, LoadInst *> *CriticalEdgePredAndLoad);
+
+  // Other helper routines.
+  bool processInstruction(Instruction *I);
+  bool processBlock(BasicBlock *BB);
+  bool iterateOnFunction(Function &F);
+  bool performPRE(Function &F);
+  bool performScalarPRE(Instruction *I);
+  bool performScalarPREInsertion(Instruction *Instr, BasicBlock *Pred,
+                                 BasicBlock *Curr, unsigned int ValNo);
+  Value *findLeader(const BasicBlock *BB, uint32_t Num);
+  void cleanupGlobalSets();
+  void removeInstruction(Instruction *I);
+  void verifyRemoved(const Instruction *I) const;
+  bool splitCriticalEdges();
+  BasicBlock *splitCriticalEdges(BasicBlock *Pred, BasicBlock *Succ);
+  bool
+  propagateEquality(Value *LHS, Value *RHS,
+                    const std::variant<BasicBlockEdge, Instruction *> &Root);
+  bool processFoldableCondBr(CondBrInst *BI);
+  void addDeadBlock(BasicBlock *BB);
+  void assignValNumForDeadCode();
+  void assignBlockRPONumber(Function &F);
+};
+
 /// Represents a particular available value that we know how to materialize.
 /// Materialization of an AvailableValue never fails.  An AvailableValue is
 /// implicitly associated with a rematerialization point which is the
 /// location of the instruction from which it was formed.
-struct llvm::GVNPass::AvailableValue {
+struct GVNPassImpl::AvailableValue {
   enum class ValType {
     SimpleVal, // A simple offsetted value that is accessed.
     LoadVal,   // A value produced by a load.
@@ -297,7 +604,7 @@ struct llvm::GVNPass::AvailableValue {
 
 /// Represents an AvailableValue which can be rematerialized at the end of
 /// the associated BasicBlock.
-struct llvm::GVNPass::AvailableValueInBlock {
+struct GVNPassImpl::AvailableValueInBlock {
   /// BB - The basic block in question.
   BasicBlock *BB = nullptr;
 
@@ -858,24 +1165,24 @@ void GVNLeaderMap::erase(uint32_t N, Instruction *I, const BasicBlock *BB) {
 //                                GVN Pass
 //===----------------------------------------------------------------------===//
 
-bool GVNPass::isScalarPREEnabled() const {
+bool GVNPassImpl::isScalarPREEnabled() const {
   return Options.AllowScalarPRE.value_or(GVNEnableScalarPRE);
 }
 
-bool GVNPass::isLoadPREEnabled() const {
+bool GVNPassImpl::isLoadPREEnabled() const {
   return Options.AllowLoadPRE.value_or(GVNEnableLoadPRE);
 }
 
-bool GVNPass::isLoadInLoopPREEnabled() const {
+bool GVNPassImpl::isLoadInLoopPREEnabled() const {
   return Options.AllowLoadInLoopPRE.value_or(GVNEnableLoadInLoopPRE);
 }
 
-bool GVNPass::isLoadPRESplitBackedgeEnabled() const {
+bool GVNPassImpl::isLoadPRESplitBackedgeEnabled() const {
   return Options.AllowLoadPRESplitBackedge.value_or(
       GVNEnableSplitBackedgeInLoadPRE);
 }
 
-bool GVNPass::isMemDepEnabled() const {
+bool GVNPassImpl::isMemDepEnabled() const {
   // MemDep and MemorySSA are mutually exclusive. parseGVNOptions() enforces
   // this for pass parameters, but the -enable-gvn-{memdep,memoryssa} cl::opt
   // overrides default independently, so honor MemorySSA winning here too.
@@ -884,11 +1191,13 @@ bool GVNPass::isMemDepEnabled() const {
   return Options.AllowMemDep.value_or(GVNEnableMemDep);
 }
 
-bool GVNPass::isMemorySSAEnabled() const {
+bool GVNPassImpl::isMemorySSAEnabled() const {
   return Options.AllowMemorySSA.value_or(GVNEnableMemorySSA);
 }
 
 PreservedAnalyses GVNPass::run(Function &F, FunctionAnalysisManager &AM) {
+  GVNPassImpl Impl(Options);
+
   // FIXME: The order of evaluation of these 'getResult' calls is very
   // significant! Re-ordering these variables will cause GVN when run alone to
   // be less effective! We should fix memdep and basic-aa to not exhibit this
@@ -897,18 +1206,19 @@ PreservedAnalyses GVNPass::run(Function &F, FunctionAnalysisManager &AM) {
   auto &DT = AM.getResult<DominatorTreeAnalysis>(F);
   auto &TLI = AM.getResult<TargetLibraryAnalysis>(F);
   auto &AA = AM.getResult<AAManager>(F);
-  auto *MemDep =
-      isMemDepEnabled() ? &AM.getResult<MemoryDependenceAnalysis>(F) : nullptr;
+  auto *MemDep = Impl.isMemDepEnabled()
+                     ? &AM.getResult<MemoryDependenceAnalysis>(F)
+                     : nullptr;
   auto &LI = AM.getResult<LoopAnalysis>(F);
   auto *MSSA = AM.getCachedResult<MemorySSAAnalysis>(F);
-  if (isMemorySSAEnabled() && !MSSA) {
+  if (Impl.isMemorySSAEnabled() && !MSSA) {
     assert(!MemDep &&
            "On-demand computation of MemSSA implies that MemDep is disabled!");
     MSSA = &AM.getResult<MemorySSAAnalysis>(F);
   }
   auto &ORE = AM.getResult<OptimizationRemarkEmitterAnalysis>(F);
-  bool Changed = runImpl(F, AC, DT, TLI, AA, MemDep, LI, &ORE,
-                         MSSA ? &MSSA->getMSSA() : nullptr);
+  bool Changed = Impl.run(F, AC, DT, TLI, AA, MemDep, LI, &ORE,
+                          MSSA ? &MSSA->getMSSA() : nullptr);
   if (!Changed)
     return PreservedAnalyses::all();
   PreservedAnalyses PA;
@@ -920,6 +1230,12 @@ PreservedAnalyses GVNPass::run(Function &F, FunctionAnalysisManager &AM) {
   return PA;
 }
 
+void GVNPassImpl::salvageAndRemoveInstruction(Instruction *I) {
+  salvageKnowledge(I, AC);
+  salvageDebugInfo(*I);
+  removeInstruction(I);
+}
+
 void GVNPass::printPipeline(
     raw_ostream &OS, function_ref<StringRef(StringRef)> MapClassName2PassName) {
   static_cast<PassInfoMixin<GVNPass> *>(this)->printPipeline(
@@ -940,12 +1256,6 @@ void GVNPass::printPipeline(
   OS << '>';
 }
 
-void GVNPass::salvageAndRemoveInstruction(Instruction *I) {
-  salvageKnowledge(I, AC);
-  salvageDebugInfo(*I);
-  removeInstruction(I);
-}
-
 enum class AvailabilityState : char {
   /// We know the block *is not* fully available. This is a fixpoint.
   Unavailable = 0,
@@ -1080,6 +1390,9 @@ static bool isValueFullyAvailableInBlock(
   return !UnavailableBB;
 }
 
+using AvailableValue = GVNPassImpl::AvailableValue;
+using AvailableValueInBlock = GVNPassImpl::AvailableValueInBlock;
+
 /// If the specified OldValue exists in ValuesPerBlock, replace its value with
 /// NewValue.
 static void replaceValuesPerBlockEntry(
@@ -1103,12 +1416,11 @@ static void replaceValuesPerBlockEntry(
 static Value *
 constructSSAForLoadSet(LoadInst *Load,
                        SmallVectorImpl<AvailableValueInBlock> &ValuesPerBlock,
-                       GVNPass &GVN) {
+                       DominatorTree &DT) {
   // Check for the fully redundant, dominating load case.  In this case, we can
   // just use the dominating value directly.
   if (ValuesPerBlock.size() == 1 &&
-      GVN.getDominatorTree().properlyDominates(ValuesPerBlock[0].BB,
-                                               Load->getParent())) {
+      DT.properlyDominates(ValuesPerBlock[0].BB, Load->getParent())) {
     assert(!ValuesPerBlock[0].AV.isUndefValue() &&
            "Dead BB dominate this block");
     return ValuesPerBlock[0].MaterializeAdjustedValue(Load);
@@ -1331,8 +1643,9 @@ static Value *findDominatingValue(const MemoryLocation &Loc, Type *LoadTy,
 }
 
 std::optional<AvailableValue>
-GVNPass::analyzeSelectAvailability(LoadInst *Load, Value *Cond, Value *TrueAddr,
-                                   Value *FalseAddr, Instruction *From) {
+GVNPassImpl::analyzeSelectAvailability(LoadInst *Load, Value *Cond,
+                                       Value *TrueAddr, Value *FalseAddr,
+                                       Instruction *From) {
   assert(TrueAddr->getType() == Load->getPointerOperandType() &&
          "Invalid address type of true side of select dependency");
   assert(FalseAddr->getType() == Load->getPointerOperandType() &&
@@ -1353,8 +1666,8 @@ GVNPass::analyzeSelectAvailability(LoadInst *Load, Value *Cond, Value *TrueAddr,
 }
 
 std::optional<AvailableValue>
-GVNPass::analyzeLoadAvailability(LoadInst *Load, const ReachingMemVal &Dep,
-                                 Value *Address) {
+GVNPassImpl::analyzeLoadAvailability(LoadInst *Load, const ReachingMemVal &Dep,
+                                     Value *Address) {
   assert(Load->isUnordered() && "rules below are incorrect for ordered access");
   assert((Dep.Kind == DepKind::Def || Dep.Kind == DepKind::Clobber) &&
          "expected a local dependence");
@@ -1495,10 +1808,10 @@ GVNPass::analyzeLoadAvailability(LoadInst *Load, const ReachingMemVal &Dep,
   return std::nullopt;
 }
 
-void GVNPass::analyzeLoadAvailability(LoadInst *Load,
-                                      SmallVectorImpl<ReachingMemVal> &Deps,
-                                      AvailValInBlkVect &ValuesPerBlock,
-                                      UnavailBlkVect &UnavailableBlocks) {
+void GVNPassImpl::analyzeLoadAvailability(LoadInst *Load,
+                                          SmallVectorImpl<ReachingMemVal> &Deps,
+                                          AvailValInBlkVect &ValuesPerBlock,
+                                          UnavailBlkVect &UnavailableBlocks) {
   // Filter out useless results (non-locals, etc).  Keep track of the blocks
   // where we have a value available in repl, also keep track of whether we see
   // dependencies that produce an unknown value for the load (such as a call
@@ -1572,8 +1885,9 @@ void GVNPass::analyzeLoadAvailability(LoadInst *Load,
 ///      v2 = load %addr
 ///      ...
 ///
-LoadInst *GVNPass::findLoadToHoistIntoPred(BasicBlock *Pred, BasicBlock *LoadBB,
-                                           LoadInst *Load) {
+LoadInst *GVNPassImpl::findLoadToHoistIntoPred(BasicBlock *Pred,
+                                               BasicBlock *LoadBB,
+                                               LoadInst *Load) {
   // For simplicity we handle a Pred has 2 successors only.
   auto *Term = Pred->getTerminator();
   if (Term->getNumSuccessors() != 2 || Term->isSpecialTerminator())
@@ -1622,7 +1936,7 @@ LoadInst *GVNPass::findLoadToHoistIntoPred(BasicBlock *Pred, BasicBlock *LoadBB,
   return nullptr;
 }
 
-void GVNPass::eliminatePartiallyRedundantLoad(
+void GVNPassImpl::eliminatePartiallyRedundantLoad(
     LoadInst *Load, AvailValInBlkVect &ValuesPerBlock,
     MapVector<BasicBlock *, Value *> &AvailableLoads,
     MapVector<BasicBlock *, LoadInst *> *CriticalEdgePredAndLoad) {
@@ -1694,7 +2008,7 @@ void GVNPass::eliminatePartiallyRedundantLoad(
   }
 
   // Perform PHI construction.
-  Value *V = constructSSAForLoadSet(Load, ValuesPerBlock, *this);
+  Value *V = constructSSAForLoadSet(Load, ValuesPerBlock, getDominatorTree());
   // constructSSAForLoadSet is responsible for combining metadata.
   ICF->removeUsersOf(Load);
   Load->replaceAllUsesWith(V);
@@ -1711,8 +2025,9 @@ void GVNPass::eliminatePartiallyRedundantLoad(
   salvageAndRemoveInstruction(Load);
 }
 
-bool GVNPass::performLoadPRE(LoadInst *Load, AvailValInBlkVect &ValuesPerBlock,
-                             UnavailBlkVect &UnavailableBlocks) {
+bool GVNPassImpl::performLoadPRE(LoadInst *Load,
+                                 AvailValInBlkVect &ValuesPerBlock,
+                                 UnavailBlkVect &UnavailableBlocks) {
   // Okay, we have *some* definitions of the value.  This means that the value
   // is available in some of our (transitive) predecessors.  Lets think about
   // doing PRE of this load.  This will involve inserting a new load into the
@@ -1964,9 +2279,9 @@ bool GVNPass::performLoadPRE(LoadInst *Load, AvailValInBlkVect &ValuesPerBlock,
   return true;
 }
 
-bool GVNPass::performLoopLoadPRE(LoadInst *Load,
-                                 AvailValInBlkVect &ValuesPerBlock,
-                                 UnavailBlkVect &UnavailableBlocks) {
+bool GVNPassImpl::performLoopLoadPRE(LoadInst *Load,
+                                     AvailValInBlkVect &ValuesPerBlock,
+                                     UnavailBlkVect &UnavailableBlocks) {
   const Loop *L = LI->getLoopFor(Load->getParent());
   // TODO: Generalize to other loop blocks that dominate the latch.
   if (!L || L->getHeader() != Load->getParent())
@@ -2055,7 +2370,7 @@ static void reportLoadElim(LoadInst *Load, Value *AvailableValue,
 
 /// Attempt to eliminate a load whose dependencies are
 /// non-local by performing PHI construction.
-bool GVNPass::processNonLocalLoad(LoadInst *Load) {
+bool GVNPassImpl::processNonLocalLoad(LoadInst *Load) {
   // Non-local speculations are not allowed under asan.
   if (Load->getFunction()->hasFnAttribute(Attribute::SanitizeAddress) ||
       Load->getFunction()->hasFnAttribute(Attribute::SanitizeHWAddress))
@@ -2098,8 +2413,8 @@ bool GVNPass::processNonLocalLoad(LoadInst *Load) {
   return processNonLocalLoad(Load, MemVals);
 }
 
-bool GVNPass::processNonLocalLoad(LoadInst *Load,
-                                  SmallVectorImpl<ReachingMemVal> &Deps) {
+bool GVNPassImpl::processNonLocalLoad(LoadInst *Load,
+                                      SmallVectorImpl<ReachingMemVal> &Deps) {
   // If we had a phi translation failure, we'll have a single entry which is a
   // clobber in the current block.  Reject this early.
   if (Deps.size() == 1 && Deps[0].Kind == DepKind::Other) {
@@ -2142,7 +2457,7 @@ bool GVNPass::processNonLocalLoad(LoadInst *Load,
     LLVM_DEBUG(dbgs() << "GVN REMOVING NONLOCAL LOAD: " << *Load << '\n');
 
     // Perform PHI construction.
-    Value *V = constructSSAForLoadSet(Load, ValuesPerBlock, *this);
+    Value *V = constructSSAForLoadSet(Load, ValuesPerBlock, getDominatorTree());
     // constructSSAForLoadSet is responsible for combining metadata.
     ICF->removeUsersOf(Load);
     Load->replaceAllUsesWith(V);
@@ -2176,7 +2491,7 @@ bool GVNPass::processNonLocalLoad(LoadInst *Load,
   return Changed;
 }
 
-bool GVNPass::processAssumeIntrinsic(AssumeInst *IntrinsicI) {
+bool GVNPassImpl::processAssumeIntrinsic(AssumeInst *IntrinsicI) {
   Value *V = IntrinsicI->getArgOperand(0);
 
   if (ConstantInt *Cond = dyn_cast<ConstantInt>(V)) {
@@ -2326,7 +2641,7 @@ maybeLoadStoreLocation(Instruction *I, bool AllowStores,
 /// Scan the users of each MemoryAccess in `ClobbersList` that belong to `BB`,
 /// looking for memory reads whose location aliases `Loc` and dominates our
 /// load.
-std::optional<GVNPass::ReachingMemVal> GVNPass::scanMemoryAccessesUsers(
+std::optional<GVNPassImpl::ReachingMemVal> GVNPassImpl::scanMemoryAccessesUsers(
     const MemoryLocation &Loc, bool IsInvariantLoad, BasicBlock *BB,
     const SmallVectorImpl<MemoryAccess *> &ClobbersList, MemorySSA &MSSA,
     BatchAAResults &AA, LoadInst *L) {
@@ -2400,7 +2715,7 @@ std::optional<GVNPass::ReachingMemVal> GVNPass::scanMemoryAccessesUsers(
 
 /// Check if a given MemoryAccess (usually a MemoryDef) actually modifies a
 /// given location. Returns a ReachingMemVal describing the dependency.
-std::optional<GVNPass::ReachingMemVal> GVNPass::accessMayModifyLocation(
+std::optional<GVNPassImpl::ReachingMemVal> GVNPassImpl::accessMayModifyLocation(
     MemoryAccess *ClobberMA, const MemoryLocation &Loc, Align LoadAlign,
     bool IsInvariantLoad, BasicBlock *BB, MemorySSA &MSSA, BatchAAResults &AA) {
   assert(ClobberMA->getBlock() == BB);
@@ -2508,10 +2823,10 @@ std::optional<GVNPass::ReachingMemVal> GVNPass::accessMayModifyLocation(
 /// Collect the predecessors of block, while doing phi-translation of the memory
 /// address and the memory clobber. Return false if the block should be marked
 /// as clobbering the memory location in an unknown way.
-bool GVNPass::collectPredecessors(BasicBlock *BB, const PHITransAddr &Addr,
-                                  MemoryAccess *ClobberMA,
-                                  DependencyBlockSet &Blocks,
-                                  SmallVectorImpl<BasicBlock *> &Worklist) {
+bool GVNPassImpl::collectPredecessors(BasicBlock *BB, const PHITransAddr &Addr,
+                                      MemoryAccess *ClobberMA,
+                                      DependencyBlockSet &Blocks,
+                                      SmallVectorImpl<BasicBlock *> &Worklist) {
   if (Addr.needsPHITranslationFromBlock(BB) &&
       !Addr.isPotentiallyPHITranslatable())
     return false;
@@ -2566,11 +2881,11 @@ bool GVNPass::collectPredecessors(BasicBlock *BB, const PHITransAddr &Addr,
 /// walk the use-def chain to the final clobber. If the chain extends beyond
 /// `BB`, continue into that block but only if it is in the previously collected
 /// set.
-void GVNPass::collectClobberList(SmallVectorImpl<MemoryAccess *> &Clobbers,
-                                 BasicBlock *BB,
-                                 const DependencyBlockInfo &StartInfo,
-                                 const DependencyBlockSet &Blocks,
-                                 MemorySSA &MSSA) {
+void GVNPassImpl::collectClobberList(SmallVectorImpl<MemoryAccess *> &Clobbers,
+                                     BasicBlock *BB,
+                                     const DependencyBlockInfo &StartInfo,
+                                     const DependencyBlockSet &Blocks,
+                                     MemorySSA &MSSA) {
   MemoryAccess *MA = StartInfo.InitialClobberMA;
   MemoryAccess *LastMA = StartInfo.ClobberMA;
 
@@ -2616,9 +2931,9 @@ void GVNPass::collectClobberList(SmallVectorImpl<MemoryAccess *> &Clobbers,
 /// * Other: we know which block defines the memory location in some way, but
 /// could not identify a precise instruction (e.g., memory already live at
 /// function entry).
-bool GVNPass::findReachingValuesForLoad(LoadInst *L,
-                                        SmallVectorImpl<ReachingMemVal> &Values,
-                                        MemorySSA &MSSA, AAResults &AAR) {
+bool GVNPassImpl::findReachingValuesForLoad(
+    LoadInst *L, SmallVectorImpl<ReachingMemVal> &Values, MemorySSA &MSSA,
+    AAResults &AAR) {
   EarliestEscapeAnalysis EA(*DT, LI);
   BatchAAResults AA(AAR, &EA);
   BasicBlock *StartBlock = L->getParent();
@@ -2800,7 +3115,7 @@ bool GVNPass::findReachingValuesForLoad(LoadInst *L,
 
 /// Attempt to eliminate a load, first by eliminating it
 /// locally, and then attempting non-local elimination if that fails.
-bool GVNPass::processLoad(LoadInst *L) {
+bool GVNPassImpl::processLoad(LoadInst *L) {
   if (!MD && !isMemorySSAEnabled())
     return false;
 
@@ -2874,7 +3189,7 @@ bool GVNPass::processLoad(LoadInst *L) {
 
 // Attempt to process masked loads which have loaded from
 // masked stores with the same mask
-bool GVNPass::processMaskedLoad(IntrinsicInst *I) {
+bool GVNPassImpl::processMaskedLoad(IntrinsicInst *I) {
   if (!MD)
     return false;
   MemDepResult Dep = MD->getDependency(I);
@@ -3069,7 +3384,7 @@ void GVNValueTable::eraseTranslateCacheEntry(uint32_t Num,
 // and then scan the list to find one whose block dominates the block in
 // question.  This is fast because dominator tree queries consist of only
 // a few comparisons of DFS numbers.
-Value *GVNPass::findLeader(const BasicBlock *BB, uint32_t Num) {
+Value *GVNPassImpl::findLeader(const BasicBlock *BB, uint32_t Num) {
   auto Leaders = LeaderTable.getLeaders(Num);
   if (Leaders.empty())
     return nullptr;
@@ -3102,7 +3417,7 @@ static bool isOnlyReachableViaThisEdge(const BasicBlockEdge &E,
   return Pred != nullptr;
 }
 
-void GVNPass::assignBlockRPONumber(Function &F) {
+void GVNPassImpl::assignBlockRPONumber(Function &F) {
   BlockRPONumber.clear();
   uint32_t NextBlockNumber = 1;
   ReversePostOrderTraversal<Function *> RPOT(&F);
@@ -3116,7 +3431,7 @@ void GVNPass::assignBlockRPONumber(Function &F) {
 /// 'RHS' everywhere in the scope.  Returns whether a change was made.
 /// The Root may either be a basic block edge (for conditions) or an
 /// instruction (for assumes).
-bool GVNPass::propagateEquality(
+bool GVNPassImpl::propagateEquality(
     Value *LHS, Value *RHS,
     const std::variant<BasicBlockEdge, Instruction *> &Root) {
   SmallVector<std::pair<Value*, Value*>, 4> Worklist;
@@ -3324,7 +3639,7 @@ bool GVNPass::propagateEquality(
 
 /// When calculating availability, handle an instruction
 /// by inserting it into the appropriate sets.
-bool GVNPass::processInstruction(Instruction *I) {
+bool GVNPassImpl::processInstruction(Instruction *I) {
   // If the instruction can be easily simplified then do so now in preference
   // to value numbering it.  Value numbering often exposes redundancies, for
   // example if it determines that %y is equal to %x then the instruction
@@ -3480,7 +3795,7 @@ bool GVNPass::processInstruction(Instruction *I) {
 }
 
 /// runOnFunction - This is the main transformation entry point for a function.
-bool GVNPass::runImpl(Function &F, AssumptionCache &RunAC, DominatorTree &RunDT,
+bool GVNPassImpl::run(Function &F, AssumptionCache &RunAC, DominatorTree &RunDT,
                       const TargetLibraryInfo &RunTLI, AAResults &RunAA,
                       MemoryDependenceResults *RunMD, LoopInfo &LI,
                       OptimizationRemarkEmitter *RunORE, MemorySSA *MSSA) {
@@ -3564,7 +3879,7 @@ bool GVNPass::runImpl(Function &F, AssumptionCache &RunAC, DominatorTree &RunDT,
   return Changed;
 }
 
-bool GVNPass::processBlock(BasicBlock *BB) {
+bool GVNPassImpl::processBlock(BasicBlock *BB) {
   if (DeadBlocks.count(BB))
     return false;
 
@@ -3585,8 +3900,9 @@ bool GVNPass::processBlock(BasicBlock *BB) {
 }
 
 // Instantiate an expression in a predecessor that lacked it.
-bool GVNPass::performScalarPREInsertion(Instruction *Instr, BasicBlock *Pred,
-                                        BasicBlock *Curr, unsigned int ValNo) {
+bool GVNPassImpl::performScalarPREInsertion(Instruction *Instr,
+                                            BasicBlock *Pred, BasicBlock *Curr,
+                                            unsigned int ValNo) {
   // Because we are going top-down through the block, all value numbers
   // will be available in the predecessor by the time we need them.  Any
   // that weren't originally present will have been instantiated earlier
@@ -3633,7 +3949,7 @@ bool GVNPass::performScalarPREInsertion(Instruction *Instr, BasicBlock *Pred,
   return true;
 }
 
-bool GVNPass::performScalarPRE(Instruction *CurInst) {
+bool GVNPassImpl::performScalarPRE(Instruction *CurInst) {
   if (isa<AllocaInst>(CurInst) || CurInst->isTerminator() ||
       isa<PHINode>(CurInst) || CurInst->getType()->isVoidTy() ||
       CurInst->mayReadFromMemory() || CurInst->mayHaveSideEffects() ||
@@ -3795,7 +4111,7 @@ bool GVNPass::performScalarPRE(Instruction *CurInst) {
 
 /// Perform a purely local form of PRE that looks for diamond
 /// control flow patterns and attempts to perform simple PRE at the join point.
-bool GVNPass::performPRE(Function &F) {
+bool GVNPassImpl::performPRE(Function &F) {
   bool Changed = false;
   for (BasicBlock *CurrentBlock : depth_first(&F.getEntryBlock())) {
     // Nothing to PRE in the entry block.
@@ -3822,7 +4138,8 @@ bool GVNPass::performPRE(Function &F) {
 
 /// Split the critical edge connecting the given two blocks, and return
 /// the block inserted to the critical edge.
-BasicBlock *GVNPass::splitCriticalEdges(BasicBlock *Pred, BasicBlock *Succ) {
+BasicBlock *GVNPassImpl::splitCriticalEdges(BasicBlock *Pred,
+                                            BasicBlock *Succ) {
   // GVN does not require loop-simplify, do not try to preserve it if it is not
   // possible.
   BasicBlock *BB = SplitCriticalEdge(
@@ -3838,7 +4155,7 @@ BasicBlock *GVNPass::splitCriticalEdges(BasicBlock *Pred, BasicBlock *Succ) {
 
 /// Split critical edges found during the previous
 /// iteration that may enable further optimization.
-bool GVNPass::splitCriticalEdges() {
+bool GVNPassImpl::splitCriticalEdges() {
   if (ToSplit.empty())
     return false;
 
@@ -3858,7 +4175,7 @@ bool GVNPass::splitCriticalEdges() {
 }
 
 /// Executes one iteration of GVN.
-bool GVNPass::iterateOnFunction(Function &F) {
+bool GVNPassImpl::iterateOnFunction(Function &F) {
   cleanupGlobalSets();
 
   // Top-down walk of the dominator tree.
@@ -3874,7 +4191,7 @@ bool GVNPass::iterateOnFunction(Function &F) {
   return Changed;
 }
 
-void GVNPass::cleanupGlobalSets() {
+void GVNPassImpl::cleanupGlobalSets() {
   VN.clear();
   LeaderTable.clear();
   BlockRPONumber.clear();
@@ -3882,7 +4199,7 @@ void GVNPass::cleanupGlobalSets() {
   InvalidBlockRPONumbers = true;
 }
 
-void GVNPass::removeInstruction(Instruction *I) {
+void GVNPassImpl::removeInstruction(Instruction *I) {
   VN.erase(I);
   if (MD) MD->removeInstruction(I);
   if (MSSAU)
@@ -3897,7 +4214,7 @@ void GVNPass::removeInstruction(Instruction *I) {
 
 /// Verify that the specified instruction does not occur in our
 /// internal data structures.
-void GVNPass::verifyRemoved(const Instruction *Inst) const {
+void GVNPassImpl::verifyRemoved(const Instruction *Inst) const {
   VN.verifyRemoved(Inst);
 }
 
@@ -3905,7 +4222,7 @@ void GVNPass::verifyRemoved(const Instruction *Inst) const {
 /// function is to add all these blocks to "DeadBlocks". For the dead blocks'
 /// live successors, update their phi nodes by replacing the operands
 /// corresponding to dead blocks with UndefVal.
-void GVNPass::addDeadBlock(BasicBlock *BB) {
+void GVNPassImpl::addDeadBlock(BasicBlock *BB) {
   SmallVector<BasicBlock *, 4> NewDead;
   SmallSetVector<BasicBlock *, 4> DF;
 
@@ -3993,7 +4310,7 @@ void GVNPass::addDeadBlock(BasicBlock *BB) {
 //     dead blocks with "UndefVal" in an hope these PHIs will optimized away.
 //
 // Return true iff *NEW* dead code are found.
-bool GVNPass::processFoldableCondBr(CondBrInst *BI) {
+bool GVNPassImpl::processFoldableCondBr(CondBrInst *BI) {
   // If a branch has two identical successors, we cannot declare either dead.
   if (BI->getSuccessor(0) == BI->getSuccessor(1))
     return false;
@@ -4018,7 +4335,7 @@ bool GVNPass::processFoldableCondBr(CondBrInst *BI) {
 // associated val-num. As it normally has far more live instructions than dead
 // instructions, it makes more sense just to "fabricate" a val-number for the
 // dead code than checking if instruction involved is dead or not.
-void GVNPass::assignValNumForDeadCode() {
+void GVNPassImpl::assignValNumForDeadCode() {
   for (BasicBlock *BB : DeadBlocks) {
     for (Instruction &Inst : *BB) {
       unsigned ValNum = VN.lookupOrAdd(&Inst);
@@ -4027,7 +4344,7 @@ void GVNPass::assignValNumForDeadCode() {
   }
 }
 
-class llvm::GVNLegacyPass : public FunctionPass {
+class GVNLegacyPass : public FunctionPass {
 public:
   static char ID; // Pass identification, replacement for typeid.
 
@@ -4049,7 +4366,7 @@ class llvm::GVNLegacyPass : public FunctionPass {
     if (Impl.isMemorySSAEnabled() && !MSSAWP)
       MSSAWP = &getAnalysis<MemorySSAWrapperPass>();
 
-    return Impl.runImpl(
+    return Impl.run(
         F, getAnalysis<AssumptionCacheTracker>().getAssumptionCache(F),
         getAnalysis<DominatorTreeWrapperPass>().getDomTree(),
         getAnalysis<TargetLibraryInfoWrapperPass>().getTLI(F),
@@ -4081,7 +4398,7 @@ class llvm::GVNLegacyPass : public FunctionPass {
   }
 
 private:
-  GVNPass Impl;
+  GVNPassImpl Impl;
 };
 
 char GVNLegacyPass::ID = 0;

>From 394ca97b8169667da874f19e83cf7c21c94000a3 Mon Sep 17 00:00:00 2001
From: Momchil Velikov <momchil.velikov at arm.com>
Date: Tue, 29 Sep 2026 17:28:29 +0100
Subject: [PATCH 2/3] [fixup] Remove unused member functions from GVNPassImpl

---
 llvm/lib/Transforms/Scalar/GVN.cpp | 7 -------
 1 file changed, 7 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/GVN.cpp b/llvm/lib/Transforms/Scalar/GVN.cpp
index 154e3106a39d3..6c35ed71483d7 100644
--- a/llvm/lib/Transforms/Scalar/GVN.cpp
+++ b/llvm/lib/Transforms/Scalar/GVN.cpp
@@ -288,13 +288,6 @@ class GVNPassImpl {
 
   GVNPassImpl(llvm::GVNOptions Options = {}) : Options(Options) {}
 
-  /// Run the pass over the function.
-  LLVM_ABI PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
-
-  LLVM_ABI void
-  printPipeline(raw_ostream &OS,
-                function_ref<StringRef(StringRef)> MapClassName2PassName);
-
   /// This removes the specified instruction from
   /// our various maps and marks it for deletion.
   LLVM_ABI void salvageAndRemoveInstruction(Instruction *I);

>From a82586daf6152161c66b73c3b4fff539f8d42223 Mon Sep 17 00:00:00 2001
From: Momchil Velikov <momchil.velikov at arm.com>
Date: Wed, 30 Sep 2026 10:56:26 +0100
Subject: [PATCH 3/3] [fixup] Clean GVNPassImpl from histerical cruft

---
 llvm/lib/Transforms/Scalar/GVN.cpp | 16 +++++++---------
 1 file changed, 7 insertions(+), 9 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/GVN.cpp b/llvm/lib/Transforms/Scalar/GVN.cpp
index 6c35ed71483d7..d7a3ff8bca76d 100644
--- a/llvm/lib/Transforms/Scalar/GVN.cpp
+++ b/llvm/lib/Transforms/Scalar/GVN.cpp
@@ -282,7 +282,6 @@ class GVNPassImpl {
   llvm::GVNOptions Options;
 
 public:
-  struct Expression;
   struct AvailableValue;
   struct AvailableValueInBlock;
 
@@ -290,23 +289,22 @@ class GVNPassImpl {
 
   /// This removes the specified instruction from
   /// our various maps and marks it for deletion.
-  LLVM_ABI void salvageAndRemoveInstruction(Instruction *I);
+  void salvageAndRemoveInstruction(Instruction *I);
 
   DominatorTree &getDominatorTree() const { return *DT; }
   AAResults *getAliasAnalysis() const { return VN.getAliasAnalysis(); }
   MemoryDependenceResults &getMemDep() const { return *MD; }
 
-  LLVM_ABI bool isScalarPREEnabled() const;
-  LLVM_ABI bool isLoadPREEnabled() const;
-  LLVM_ABI bool isLoadInLoopPREEnabled() const;
-  LLVM_ABI bool isLoadPRESplitBackedgeEnabled() const;
-  LLVM_ABI bool isMemDepEnabled() const;
-  LLVM_ABI bool isMemorySSAEnabled() const;
+  bool isScalarPREEnabled() const;
+  bool isLoadPREEnabled() const;
+  bool isLoadInLoopPREEnabled() const;
+  bool isLoadPRESplitBackedgeEnabled() const;
+  bool isMemDepEnabled() const;
+  bool isMemorySSAEnabled() const;
 
 private:
   friend class llvm::GVNPass;
   friend class GVNLegacyPass;
-  friend struct DenseMapInfo<Expression>;
 
   MemoryDependenceResults *MD = nullptr;
   DominatorTree *DT = nullptr;



More information about the llvm-commits mailing list