[llvm] [Analysis] Track offsets in PtrUseVisitor to revisit when required (PR #179726)

Jameson Nash via llvm-commits llvm-commits at lists.llvm.org
Thu Feb 5 07:21:21 PST 2026


https://github.com/vtjnash updated https://github.com/llvm/llvm-project/pull/179726

>From 9dc971f9a43fbaa003738cd274a30b7c1d938037 Mon Sep 17 00:00:00 2001
From: Jameson Nash <vtjnash+github at gmail.com>
Date: Wed, 4 Feb 2026 16:11:02 +0000
Subject: [PATCH] [Analysis] Track offsets in PtrUseVisitor to revisit when
 required

Change PtrUseVisitor's VisitedUses from SmallPtrSet to DenseMap that
tracks offsets, ensuring correctness of the Workqueue (at the cost of
possibly repeating an instruction visit).

Previously, once a Use was visited, it would never be revisited even if
encountered with a different offset. This prevented proper detection of
offset conflicts in scenarios like PHI nodes merging GEPs with different
offsets. All existing clients currently handle that case correctly (SROA
because it does not enqueue users of PHI or Select, Coro because it
aborts the program when this occurs, and NVPTX because it ignores the
offset always). So instead add a unit test to show it can handle this
correctly for future users.

This precision-based approach can be defeated by this clever gadget however:
```
arraydestroy.body:
  %arraydestroy.elementPast = phi ptr [ %.arraydestroy.body, %arraydestroy.body.from.cleanup.action ], [ %arraydestroy.element.arraydestroy.body, %arraydestroy.body.from.arraydestroy.body ]
  %arraydestroy.element = getelementptr inbounds %struct.Printy, ptr %arraydestroy.elementPast, i64 -1
  call void @_ZN6PrintyD1Ev(ptr noundef nonnull align 8 dereferenceable(8) %arraydestroy.element) #2
  %arraydestroy.done = icmp eq ptr %arraydestroy.element, %arr
  br i1 %arraydestroy.done, label %arraydestroy.done6, label %arraydestroy.body.from.arraydestroy.body
```

So we also need to track which instructions are currently in the direct
"path" to this instruction.

Co-Authored-By: Claude Sonnet 4.5 <noreply at anthropic.com>
---
 llvm/include/llvm/Analysis/PtrUseVisitor.h    |  72 +++-
 llvm/lib/Analysis/PtrUseVisitor.cpp           |  31 +-
 llvm/lib/Target/NVPTX/NVPTXLowerArgs.cpp      |  45 +--
 .../lib/Transforms/Coroutines/CoroCleanup.cpp |  10 +-
 llvm/unittests/Analysis/CMakeLists.txt        |   1 +
 llvm/unittests/Analysis/PtrUseVisitorTest.cpp | 351 ++++++++++++++++++
 6 files changed, 452 insertions(+), 58 deletions(-)
 create mode 100644 llvm/unittests/Analysis/PtrUseVisitorTest.cpp

diff --git a/llvm/include/llvm/Analysis/PtrUseVisitor.h b/llvm/include/llvm/Analysis/PtrUseVisitor.h
index 0858d8aee2186..945a4a0927867 100644
--- a/llvm/include/llvm/Analysis/PtrUseVisitor.h
+++ b/llvm/include/llvm/Analysis/PtrUseVisitor.h
@@ -23,6 +23,8 @@
 #define LLVM_ANALYSIS_PTRUSEVISITOR_H
 
 #include "llvm/ADT/APInt.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/DenseSet.h"
 #include "llvm/ADT/PointerIntPair.h"
 #include "llvm/ADT/SmallPtrSet.h"
 #include "llvm/ADT/SmallVector.h"
@@ -127,20 +129,23 @@ class PtrUseVisitorBase {
 
   /// A struct of the data needed to visit a particular use.
   ///
-  /// This is used to maintain a worklist fo to-visit uses. This is used to
+  /// This is used to maintain a worklist of to-visit uses. This is used to
   /// make the visit be iterative rather than recursive.
   struct UseToVisit {
-    using UseAndIsOffsetKnownPair = PointerIntPair<Use *, 1, bool>;
+    using UseAndIsOffsetKnownPair = PointerIntPair<Use *, 1, unsigned>;
+    using UseAndPassInfo = PointerIntPair<UseAndIsOffsetKnownPair, 1, unsigned>;
 
-    UseAndIsOffsetKnownPair UseAndIsOffsetKnown;
+    UseAndPassInfo UseInfo;
     APInt Offset;
   };
 
   /// The worklist of to-visit uses.
   SmallVector<UseToVisit, 8> Worklist;
 
-  /// A set of visited uses to break cycles in unreachable code.
-  SmallPtrSet<Use *, 8> VisitedUses;
+  /// Set of (Use, (IsOffsetKnown & Offset)) pairs that have been fully visited.
+  /// Used to prevent duplicate visits in post-order traversal.
+  using UseWithOffset = std::pair<UseToVisit::UseAndIsOffsetKnownPair, APInt>;
+  llvm::DenseSet<UseWithOffset> Visited;
 
   /// @}
 
@@ -158,6 +163,10 @@ class PtrUseVisitorBase {
   /// The constant offset of the use if that is known.
   APInt Offset;
 
+  /// Path-dependency of walk to first DFS encounter.
+  /// Used for detecting (and avoiding) possible cycles.
+  llvm::DenseMap<Instruction *, APInt> InstsInPath;
+
   /// @}
 
   /// Note that the constructor is protected because this class must be a base
@@ -196,9 +205,9 @@ class PtrUseVisitorBase {
 /// visited! This is because users can be visited multiple times due to
 /// multiple, different uses of pointers derived from the same base.
 ///
-/// A particular Use will only be visited once, but a User may be visited
-/// multiple times, once per Use. This visits may notably have different
-/// offsets.
+/// A particular Use may be visited multiple times if reached with different
+/// offsets. A User may be visited multiple times, once per Use, and each
+/// Use may be visited multiple times with different offsets.
 ///
 /// All visit methods on the underlying InstVisitor return a boolean. This
 /// return short-circuits the visit, stopping it immediately.
@@ -221,32 +230,57 @@ class PtrUseVisitor : protected InstVisitor<DerivedT>,
   /// \returns An info struct about the pointer. See \c PtrInfo for details.
   /// We may also need to process Argument pointers, so the input uses is
   /// a common Value type.
-  PtrInfo visitPtr(Value &I) {
+  PtrInfo visitPtr(Value &I, bool TrackOffsets = true) {
     // This must be a pointer type. Get an integer type suitable to hold
     // offsets on this pointer.
     // FIXME: Support a vector of pointers.
     assert(I.getType()->isPointerTy());
     assert(isa<Instruction>(I) || isa<Argument>(I));
     IntegerType *IntIdxTy = cast<IntegerType>(DL.getIndexType(I.getType()));
-    IsOffsetKnown = true;
-    Offset = APInt(IntIdxTy->getBitWidth(), 0);
+    IsOffsetKnown = TrackOffsets;
+    Offset = TrackOffsets ? APInt(IntIdxTy->getBitWidth(), 0) : APInt();
     PI.reset();
 
     // Enqueue the uses of this pointer.
     enqueueUsers(I);
 
     // Visit all the uses off the worklist until it is empty.
+    // We use revisit in post-order to pop the element from InstsInPath after
+    // processing all children.
     while (!Worklist.empty()) {
       UseToVisit ToVisit = Worklist.pop_back_val();
-      U = ToVisit.UseAndIsOffsetKnown.getPointer();
-      IsOffsetKnown = ToVisit.UseAndIsOffsetKnown.getInt();
-      if (IsOffsetKnown)
-        Offset = std::move(ToVisit.Offset);
-
+      U = ToVisit.UseInfo.getPointer().getPointer();
+      IsOffsetKnown = ToVisit.UseInfo.getPointer().getInt();
+      bool SecondPass = ToVisit.UseInfo.getInt();
       Instruction *I = cast<Instruction>(U->getUser());
-      static_cast<DerivedT*>(this)->visit(I);
-      if (PI.isAborted())
-        break;
+
+      if (SecondPass) {
+        InstsInPath.erase(I);
+      } else {
+        // First time seeing this (Use, Offset): enqueue users.
+        Offset = std::move(ToVisit.Offset);
+        ToVisit.Offset = APInt();
+
+        // Handle pointer-propagating operations: adjust state and enqueue
+        // users. PHI and Select are not handled here as some clients have
+        // conditional logic for these.
+        if (auto *GEPI = dyn_cast<GetElementPtrInst>(I)) {
+          if (!GEPI->use_empty()) {
+            if (!adjustOffsetForGEP(*GEPI)) {
+              IsOffsetKnown = false;
+              Offset = APInt();
+            }
+          }
+        }
+
+        // Re-enqueue for visiting after users are processed
+        ToVisit.UseInfo.setInt(true);
+        Worklist.push_back(std::move(ToVisit));
+        InstsInPath[I] = Offset;
+        static_cast<DerivedT *>(this)->visit(I);
+        if (PI.isAborted())
+          break;
+      }
     }
     return PI;
   }
diff --git a/llvm/lib/Analysis/PtrUseVisitor.cpp b/llvm/lib/Analysis/PtrUseVisitor.cpp
index 9c79546f491ef..0451715c73292 100644
--- a/llvm/lib/Analysis/PtrUseVisitor.cpp
+++ b/llvm/lib/Analysis/PtrUseVisitor.cpp
@@ -19,13 +19,32 @@ using namespace llvm;
 
 void detail::PtrUseVisitorBase::enqueueUsers(Value &I) {
   for (Use &U : I.uses()) {
-    if (VisitedUses.insert(&U).second) {
-      UseToVisit NewU = {
-        UseToVisit::UseAndIsOffsetKnownPair(&U, IsOffsetKnown),
-        Offset
-      };
-      Worklist.push_back(std::move(NewU));
+    bool OffsetKnown = IsOffsetKnown;
+    APInt OffsetCopy = Offset;
+
+    if (OffsetKnown) {
+      // If we're about to visit a PHI that's already in our path,
+      // we hit a possibly-infinite cycle. If it had the same value as before,
+      // then we are at the fixed point. Otherwise, widen this offset to
+      // unknown.
+      auto I = cast<Instruction>(U.getUser());
+      auto It = InstsInPath.find(I);
+      if (It != InstsInPath.end()) {
+        if (It->second == OffsetCopy)
+          // Same offset as when we first encountered this PHI, skip
+          continue;
+        // Different offset, mark as unknown
+        OffsetKnown = false;
+        OffsetCopy = APInt();
+      }
     }
+
+    UseWithOffset Key = {{&U, OffsetKnown}, OffsetCopy};
+    if (!Visited.insert(std::move(Key)).second)
+      continue;
+
+    UseToVisit NewU = {{{&U, OffsetKnown}, false}, std::move(OffsetCopy)};
+    Worklist.push_back(std::move(NewU));
   }
 }
 
diff --git a/llvm/lib/Target/NVPTX/NVPTXLowerArgs.cpp b/llvm/lib/Target/NVPTX/NVPTXLowerArgs.cpp
index e2bbe57c0085c..7095213d8706e 100644
--- a/llvm/lib/Target/NVPTX/NVPTXLowerArgs.cpp
+++ b/llvm/lib/Target/NVPTX/NVPTXLowerArgs.cpp
@@ -440,38 +440,21 @@ struct ArgUseChecker : PtrUseVisitor<ArgUseChecker> {
       : PtrUseVisitor(DL), IsGridConstant(IsGridConstant) {}
 
   PtrInfo visitArgPtr(Argument &A) {
-    assert(A.getType()->isPointerTy());
-    IntegerType *IntIdxTy = cast<IntegerType>(DL.getIndexType(A.getType()));
-    IsOffsetKnown = false;
-    Offset = APInt(IntIdxTy->getBitWidth(), 0);
-    PI.reset();
+    LLVM_DEBUG(dbgs() << "Checking Argument " << A << "\n");
     Conditionals.clear();
 
-    LLVM_DEBUG(dbgs() << "Checking Argument " << A << "\n");
-    // Enqueue the uses of this pointer.
-    enqueueUsers(A);
-
-    // Visit all the uses off the worklist until it is empty.
-    // Note that unlike PtrUseVisitor we intentionally do not track offsets.
-    // We're only interested in how we use the pointer.
-    while (!(Worklist.empty() || PI.isAborted())) {
-      UseToVisit ToVisit = Worklist.pop_back_val();
-      U = ToVisit.UseAndIsOffsetKnown.getPointer();
-      Instruction *I = cast<Instruction>(U->getUser());
-      if (isa<PHINode>(I) || isa<SelectInst>(I))
-        Conditionals.insert(I);
-      LLVM_DEBUG(dbgs() << "Processing " << *I << "\n");
-      Base::visit(I);
-    }
-    if (PI.isEscaped())
-      LLVM_DEBUG(dbgs() << "Argument pointer escaped: " << *PI.getEscapingInst()
-                        << "\n");
-    else if (PI.isAborted())
-      LLVM_DEBUG(dbgs() << "Pointer use needs a copy: " << *PI.getAbortingInst()
-                        << "\n");
+    // Visit without offset tracking - we only care how the pointer is used
+    PtrInfo Result = visitPtr(A, /*TrackOffsets=*/false);
+
+    if (Result.isEscaped())
+      LLVM_DEBUG(dbgs() << "Argument pointer escaped: "
+                        << *Result.getEscapingInst() << "\n");
+    else if (Result.isAborted())
+      LLVM_DEBUG(dbgs() << "Pointer use needs a copy: "
+                        << *Result.getAbortingInst() << "\n");
     LLVM_DEBUG(dbgs() << "Traversed " << Conditionals.size()
                       << " conditionals\n");
-    return PI;
+    return Result;
   }
 
   void visitStoreInst(StoreInst &SI) {
@@ -498,10 +481,12 @@ struct ArgUseChecker : PtrUseVisitor<ArgUseChecker> {
   }
   void visitPHINodeOrSelectInst(Instruction &I) {
     assert(isa<PHINode>(I) || isa<SelectInst>(I));
+    Conditionals.insert(&I);
+    enqueueUsers(I);
   }
   // PHI and select just pass through the pointers.
-  void visitPHINode(PHINode &PN) { enqueueUsers(PN); }
-  void visitSelectInst(SelectInst &SI) { enqueueUsers(SI); }
+  void visitPHINode(PHINode &PN) { visitPHINodeOrSelectInst(PN); }
+  void visitSelectInst(SelectInst &SI) { visitPHINodeOrSelectInst(SI); }
 
   void visitMemTransferInst(MemTransferInst &II) {
     if (*U == II.getRawDest() && !IsGridConstant)
diff --git a/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp b/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp
index c44eaddd7ee55..7fac35f7ebc91 100644
--- a/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp
+++ b/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp
@@ -201,10 +201,10 @@ void Lowerer::lowerCoroNoop(IntrinsicInst *II) {
 }
 
 void NoopCoroElider::run(IntrinsicInst *II) {
-  visitPtr(*II);
+  visitPtr(*II, /*TrackOffsets=*/false);
 
   Worklist.clear();
-  VisitedUses.clear();
+  Visited.clear();
 }
 
 void NoopCoroElider::visitCallBase(CallBase &CB) {
@@ -250,8 +250,12 @@ bool NoopCoroElider::tryEraseCallInvoke(Instruction *I) {
 
 void NoopCoroElider::eraseFromWorklist(Instruction *I) {
   erase_if(Worklist, [I](UseToVisit &U) {
-    return I == U.UseAndIsOffsetKnown.getPointer()->getUser();
+    return I == U.UseInfo.getPointer().getPointer()->getUser();
   });
+  remove_if(Visited, [I](UseWithOffset &U) {
+    return I == U.first.getPointer()->getUser();
+  });
+  InstsInPath.erase(I);
 }
 
 static bool declaresCoroCleanupIntrinsics(const Module &M) {
diff --git a/llvm/unittests/Analysis/CMakeLists.txt b/llvm/unittests/Analysis/CMakeLists.txt
index 50bf4539e7984..5c487744c2b03 100644
--- a/llvm/unittests/Analysis/CMakeLists.txt
+++ b/llvm/unittests/Analysis/CMakeLists.txt
@@ -49,6 +49,7 @@ set(ANALYSIS_TEST_SOURCES
   PluginInlineAdvisorAnalysisTest.cpp
   PluginInlineOrderAnalysisTest.cpp
   ProfileSummaryInfoTest.cpp
+  PtrUseVisitorTest.cpp
   ReplaceWithVecLibTest.cpp
   ScalarEvolutionTest.cpp
   SparsePropagation.cpp
diff --git a/llvm/unittests/Analysis/PtrUseVisitorTest.cpp b/llvm/unittests/Analysis/PtrUseVisitorTest.cpp
new file mode 100644
index 0000000000000..c4e429ca201df
--- /dev/null
+++ b/llvm/unittests/Analysis/PtrUseVisitorTest.cpp
@@ -0,0 +1,351 @@
+//===- PtrUseVisitorTest.cpp - PtrUseVisitor unit tests ------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Analysis/PtrUseVisitor.h"
+#include "llvm/IR/BasicBlock.h"
+#include "llvm/IR/Constants.h"
+#include "llvm/IR/DataLayout.h"
+#include "llvm/IR/Function.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/IR/Module.h"
+#include "llvm/IR/Type.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+
+namespace {
+
+struct TestVisitor : public PtrUseVisitor<TestVisitor> {
+  using Base = PtrUseVisitor<TestVisitor>;
+
+  struct Visit {
+    Instruction *I;
+    bool OffsetKnown;
+    APInt Offset;
+    SmallDenseMap<Instruction *, APInt, 2> InstsInPath;
+  };
+
+  std::vector<Visit> Visits;
+
+  TestVisitor(const DataLayout &DL) : Base(DL) {}
+
+  void visitPHINode(PHINode &PN) { enqueueUsers(PN); }
+
+  void visitSelectInst(SelectInst &SI) { enqueueUsers(SI); }
+
+  void visitStoreInst(StoreInst &SI) {
+    Visits.push_back({&SI, IsOffsetKnown, Offset, InstsInPath});
+    if (SI.getValueOperand() == U->get())
+      PI.setEscaped(&SI);
+  }
+
+  void visitLoadInst(LoadInst &LI) {
+    Visits.push_back({&LI, IsOffsetKnown, Offset, InstsInPath});
+  }
+};
+
+TEST(PtrUseVisitorTest, PHIWithConflictingOffsets) {
+  LLVMContext C;
+  Module M("PtrUseVisitorTest", C);
+
+  Type *I64Ty = Type::getInt64Ty(C);
+  StructType *StructTy = StructType::get(C, {I64Ty, I64Ty, I64Ty});
+  Type *PtrTy = PointerType::get(C, 0);
+  Type *I1Ty = Type::getInt1Ty(C);
+  Type *VoidTy = Type::getVoidTy(C);
+
+  // Create a function with a PHI that merges GEPs with different offsets
+  Function *F = Function::Create(FunctionType::get(VoidTy, {I1Ty}, false),
+                                 Function::ExternalLinkage, "f", M);
+
+  BasicBlock *Entry = BasicBlock::Create(C, "entry", F);
+  BasicBlock *Then = BasicBlock::Create(C, "then", F);
+  BasicBlock *Else = BasicBlock::Create(C, "else", F);
+  BasicBlock *Merge = BasicBlock::Create(C, "merge", F);
+
+  AllocaInst *Alloca = new AllocaInst(StructTy, 0, "alloca", Entry);
+  BranchInst::Create(Then, Else, F->getArg(0), Entry);
+
+  Value *GEP0 = GetElementPtrInst::CreateInBounds(
+      StructTy, Alloca,
+      {ConstantInt::get(I64Ty, 0), ConstantInt::get(I64Ty, 0)}, "gep0", Then);
+  BranchInst::Create(Merge, Then);
+
+  Value *GEP1 = GetElementPtrInst::CreateInBounds(
+      StructTy, Alloca,
+      {ConstantInt::get(I64Ty, 0), ConstantInt::get(I64Ty, 1)}, "gep1", Else);
+  BranchInst::Create(Merge, Else);
+
+  PHINode *Phi = PHINode::Create(PtrTy, 2, "phi", Merge);
+  Phi->addIncoming(GEP0, Then);
+  Phi->addIncoming(GEP1, Else);
+
+  LoadInst *Load = new LoadInst(I64Ty, Phi, "load", Merge);
+  ReturnInst::Create(C, Merge);
+
+  TestVisitor TV(M.getDataLayout());
+  TV.visitPtr(*Alloca);
+
+  // Check that load is visited twice with different offsets
+  ASSERT_EQ(TV.Visits.size(), 2u);
+  EXPECT_EQ(TV.Visits[0].I, Load);
+  EXPECT_EQ(TV.Visits[1].I, Load);
+  EXPECT_TRUE(TV.Visits[0].OffsetKnown);
+  EXPECT_TRUE(TV.Visits[1].OffsetKnown);
+  EXPECT_NE(TV.Visits[0].Offset, TV.Visits[1].Offset);
+  EXPECT_TRUE((TV.Visits[0].Offset == 0 && TV.Visits[1].Offset == 8) ||
+              (TV.Visits[0].Offset == 8 && TV.Visits[1].Offset == 0));
+  // InstsInPath tracks all instructions in the path (Alloca, GEP, PHI)
+  EXPECT_GE(TV.Visits[0].InstsInPath.size(), 1u);
+  EXPECT_TRUE(TV.Visits[0].InstsInPath.contains(Phi));
+  EXPECT_GE(TV.Visits[1].InstsInPath.size(), 1u);
+  EXPECT_TRUE(TV.Visits[1].InstsInPath.contains(Phi));
+}
+
+TEST(PtrUseVisitorTest, PHIWithSameOffset) {
+  LLVMContext C;
+  Module M("PtrUseVisitorTest", C);
+
+  Type *I64Ty = Type::getInt64Ty(C);
+  StructType *StructTy = StructType::get(C, {I64Ty, I64Ty, I64Ty});
+  Type *PtrTy = PointerType::get(C, 0);
+  Type *I1Ty = Type::getInt1Ty(C);
+  Type *VoidTy = Type::getVoidTy(C);
+
+  // Create a function with a PHI that merges GEPs with the same offset
+  Function *F = Function::Create(FunctionType::get(VoidTy, {I1Ty}, false),
+                                 Function::ExternalLinkage, "f", M);
+
+  BasicBlock *Entry = BasicBlock::Create(C, "entry", F);
+  BasicBlock *Then = BasicBlock::Create(C, "then", F);
+  BasicBlock *Else = BasicBlock::Create(C, "else", F);
+  BasicBlock *Merge = BasicBlock::Create(C, "merge", F);
+
+  AllocaInst *Alloca = new AllocaInst(StructTy, 0, "alloca", Entry);
+  BranchInst::Create(Then, Else, F->getArg(0), Entry);
+
+  Value *GEP0 = GetElementPtrInst::CreateInBounds(
+      StructTy, Alloca,
+      {ConstantInt::get(I64Ty, 0), ConstantInt::get(I64Ty, 1)}, "gep0", Then);
+  BranchInst::Create(Merge, Then);
+
+  Value *GEP1 = GetElementPtrInst::CreateInBounds(
+      StructTy, Alloca,
+      {ConstantInt::get(I64Ty, 0), ConstantInt::get(I64Ty, 1)}, "gep1", Else);
+  BranchInst::Create(Merge, Else);
+
+  PHINode *Phi = PHINode::Create(PtrTy, 2, "phi", Merge);
+  Phi->addIncoming(GEP0, Then);
+  Phi->addIncoming(GEP1, Else);
+
+  LoadInst *Load = new LoadInst(I64Ty, Phi, "load", Merge);
+  ReturnInst::Create(C, Merge);
+
+  TestVisitor TV(M.getDataLayout());
+  TV.visitPtr(*Alloca);
+
+  // Check that load is visited once when offsets are the same
+  ASSERT_EQ(TV.Visits.size(), 1u);
+  EXPECT_EQ(TV.Visits[0].I, Load);
+  EXPECT_TRUE(TV.Visits[0].OffsetKnown);
+  EXPECT_EQ(TV.Visits[0].Offset, 8u);
+}
+
+TEST(PtrUseVisitorTest, UnknownAndKnownOffsets) {
+  LLVMContext C;
+  Module M("PtrUseVisitorTest", C);
+
+  Type *I64Ty = Type::getInt64Ty(C);
+  StructType *StructTy = StructType::get(C, {I64Ty, I64Ty, I64Ty});
+  Type *PtrTy = PointerType::get(C, 0);
+  Type *VoidTy = Type::getVoidTy(C);
+
+  // Create a function with both unknown and known offset GEPs
+  Function *F = Function::Create(FunctionType::get(VoidTy, {PtrTy}, false),
+                                 Function::ExternalLinkage, "f", M);
+
+  BasicBlock *Entry = BasicBlock::Create(C, "entry", F);
+
+  AllocaInst *Alloca = new AllocaInst(StructTy, 0, "alloca", Entry);
+
+  Value *VarGEP = GetElementPtrInst::Create(I64Ty, Alloca, {F->getArg(0)},
+                                            "var_gep", Entry);
+  LoadInst *Load1 = new LoadInst(I64Ty, VarGEP, "load1", Entry);
+
+  Value *ConstGEP = GetElementPtrInst::CreateInBounds(
+      StructTy, Alloca,
+      {ConstantInt::get(I64Ty, 0), ConstantInt::get(I64Ty, 1)}, "const_gep",
+      Entry);
+  LoadInst *Load2 = new LoadInst(I64Ty, ConstGEP, "load2", Entry);
+
+  ReturnInst::Create(C, Entry);
+
+  TestVisitor TV(M.getDataLayout());
+  TV.visitPtr(*Alloca);
+
+  // Check that both unknown and known offset loads are visited
+  ASSERT_EQ(TV.Visits.size(), 2u);
+  EXPECT_EQ(TV.Visits[0].I, Load1);
+  EXPECT_FALSE(TV.Visits[0].OffsetKnown);
+  EXPECT_EQ(TV.Visits[1].I, Load2);
+  EXPECT_TRUE(TV.Visits[1].OffsetKnown);
+  EXPECT_EQ(TV.Visits[1].Offset, 8u);
+}
+
+TEST(PtrUseVisitorTest, NoOffsetTracking) {
+  LLVMContext C;
+  Module M("PtrUseVisitorTest", C);
+
+  Type *I64Ty = Type::getInt64Ty(C);
+  StructType *StructTy = StructType::get(C, {I64Ty, I64Ty, I64Ty});
+  Type *PtrTy = PointerType::get(C, 0);
+  Type *I1Ty = Type::getInt1Ty(C);
+  Type *VoidTy = Type::getVoidTy(C);
+
+  // Create a function with a PHI merging GEPs, visited without offset tracking
+  Function *F = Function::Create(FunctionType::get(VoidTy, {I1Ty}, false),
+                                 Function::ExternalLinkage, "f", M);
+
+  BasicBlock *Entry = BasicBlock::Create(C, "entry", F);
+  BasicBlock *Then = BasicBlock::Create(C, "then", F);
+  BasicBlock *Else = BasicBlock::Create(C, "else", F);
+  BasicBlock *Merge = BasicBlock::Create(C, "merge", F);
+
+  AllocaInst *Alloca = new AllocaInst(StructTy, 0, "alloca", Entry);
+  BranchInst::Create(Then, Else, F->getArg(0), Entry);
+
+  Value *GEP0 = GetElementPtrInst::CreateInBounds(
+      StructTy, Alloca,
+      {ConstantInt::get(I64Ty, 0), ConstantInt::get(I64Ty, 0)}, "gep0", Then);
+  BranchInst::Create(Merge, Then);
+
+  Value *GEP1 = GetElementPtrInst::CreateInBounds(
+      StructTy, Alloca,
+      {ConstantInt::get(I64Ty, 0), ConstantInt::get(I64Ty, 1)}, "gep1", Else);
+  BranchInst::Create(Merge, Else);
+
+  PHINode *Phi = PHINode::Create(PtrTy, 2, "phi", Merge);
+  Phi->addIncoming(GEP0, Then);
+  Phi->addIncoming(GEP1, Else);
+
+  LoadInst *Load = new LoadInst(I64Ty, Phi, "load", Merge);
+  ReturnInst::Create(C, Merge);
+
+  TestVisitor TV(M.getDataLayout());
+  TV.visitPtr(*Alloca, /*TrackOffsets=*/false);
+
+  // Check that load is visited once when offset tracking is disabled
+  ASSERT_EQ(TV.Visits.size(), 1u);
+  EXPECT_EQ(TV.Visits[0].I, Load);
+  EXPECT_FALSE(TV.Visits[0].OffsetKnown);
+}
+
+TEST(PtrUseVisitorTest, PHITracking) {
+  LLVMContext C;
+  Module M("PtrUseVisitorTest", C);
+
+  Type *I64Ty = Type::getInt64Ty(C);
+  StructType *StructTy = StructType::get(C, {I64Ty, I64Ty, I64Ty});
+  Type *PtrTy = PointerType::get(C, 0);
+  Type *I1Ty = Type::getInt1Ty(C);
+  Type *VoidTy = Type::getVoidTy(C);
+
+  // Create a function with a PHI merging GEPs with different offsets
+  Function *F = Function::Create(FunctionType::get(VoidTy, {I1Ty}, false),
+                                 Function::ExternalLinkage, "f", M);
+
+  BasicBlock *Entry = BasicBlock::Create(C, "entry", F);
+  BasicBlock *Then = BasicBlock::Create(C, "then", F);
+  BasicBlock *Else = BasicBlock::Create(C, "else", F);
+  BasicBlock *Merge = BasicBlock::Create(C, "merge", F);
+
+  AllocaInst *Alloca = new AllocaInst(StructTy, 0, "alloca", Entry);
+  BranchInst::Create(Then, Else, F->getArg(0), Entry);
+
+  Value *GEP0 = GetElementPtrInst::CreateInBounds(
+      StructTy, Alloca,
+      {ConstantInt::get(I64Ty, 0), ConstantInt::get(I64Ty, 0)}, "gep0", Then);
+  BranchInst::Create(Merge, Then);
+
+  Value *GEP1 = GetElementPtrInst::CreateInBounds(
+      StructTy, Alloca,
+      {ConstantInt::get(I64Ty, 0), ConstantInt::get(I64Ty, 1)}, "gep1", Else);
+  BranchInst::Create(Merge, Else);
+
+  PHINode *Phi = PHINode::Create(PtrTy, 2, "phi", Merge);
+  Phi->addIncoming(GEP0, Then);
+  Phi->addIncoming(GEP1, Else);
+
+  LoadInst *Load = new LoadInst(I64Ty, Phi, "load", Merge);
+  ReturnInst::Create(C, Merge);
+
+  TestVisitor TV(M.getDataLayout());
+  TV.visitPtr(*Alloca);
+
+  // Check that both visits tracked the instructions in the path
+  ASSERT_EQ(TV.Visits.size(), 2u);
+  // InstsInPath tracks all instructions in the path (Alloca, GEP, PHI, etc.)
+  EXPECT_GE(TV.Visits[0].InstsInPath.size(), 1u);
+  EXPECT_GE(TV.Visits[1].InstsInPath.size(), 1u);
+  EXPECT_EQ(TV.Visits[0].InstsInPath.count(Phi), 1u);
+  EXPECT_EQ(TV.Visits[1].InstsInPath.count(Phi), 1u);
+  // Check that the PHI was recorded with the correct offsets (0 and 8)
+  EXPECT_TRUE((TV.Visits[0].InstsInPath[Phi] == 0 &&
+               TV.Visits[1].InstsInPath[Phi] == 8) ||
+              (TV.Visits[0].InstsInPath[Phi] == 8 &&
+               TV.Visits[1].InstsInPath[Phi] == 0));
+}
+
+TEST(PtrUseVisitorTest, PHICycle) {
+  LLVMContext C;
+  Module M("PtrUseVisitorTest", C);
+
+  Type *I64Ty = Type::getInt64Ty(C);
+  Type *PtrTy = PointerType::get(C, 0);
+  Type *I1Ty = Type::getInt1Ty(C);
+  Type *VoidTy = Type::getVoidTy(C);
+
+  // Create a function with a PHI cycle
+  Function *F =
+      Function::Create(FunctionType::get(VoidTy, {PtrTy, I1Ty}, false),
+                       Function::ExternalLinkage, "f", M);
+
+  BasicBlock *Entry = BasicBlock::Create(C, "entry", F);
+  BasicBlock *Loop = BasicBlock::Create(C, "loop", F);
+  BasicBlock *Exit = BasicBlock::Create(C, "exit", F);
+
+  BranchInst::Create(Loop, Entry);
+
+  PHINode *Phi = PHINode::Create(PtrTy, 2, "phi", Loop);
+  Phi->addIncoming(F->getArg(0), Entry);
+
+  Value *GEP = GetElementPtrInst::Create(
+      I64Ty, Phi, {ConstantInt::get(I64Ty, 1)}, "gep", Loop);
+  Phi->addIncoming(GEP, Loop);
+
+  BranchInst::Create(Loop, Exit, F->getArg(1), Loop);
+
+  LoadInst *Load = new LoadInst(I64Ty, Phi, "load", Exit);
+  ReturnInst::Create(C, Exit);
+
+  TestVisitor TV(M.getDataLayout());
+  TV.visitPtr(*F->getArg(0));
+
+  // The PHI creates a cycle, so when we revisit it, offset should become
+  // unknown
+  bool FoundUnknownOffset = false;
+  for (const auto &Visit : TV.Visits) {
+    if (!Visit.OffsetKnown) {
+      FoundUnknownOffset = true;
+      break;
+    }
+  }
+  EXPECT_TRUE(FoundUnknownOffset);
+}
+
+} // namespace



More information about the llvm-commits mailing list