[llvm] [IR] Use iteration limit in stripAndAccumulateConstantOffsets (PR #190473)

via llvm-commits llvm-commits at lists.llvm.org
Sat Apr 4 09:55:28 PDT 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-ir

Author: Alexis Engelke (aengelke)

<details>
<summary>Changes</summary>

Using a SmallPtrSet is not free, even for small counts. The actual
iteration numbers can be quite high for loops with a high unroll count
-- UnrollLoop will create a long GEP chain and then InstSimplify will
call the function for every part of the chain. (This happens in multiple
occasions: e.g. computePointerICmp and simplifyGEPInst will both walk
the chain.) Therefore, even without endless loops, an iteration limit
seems appropriate here.

For Clang, the statistics are:

  47985 N=6..9
   4831 N=10..14
   4054 N=15..19
   3731 N=20..24
   3628 N=25..29
    705 N=30
    475 N=31
    242 N=32
     12 N=33
    ...
      4 N>100 (single function with a high unroll count)

Also add missing loop exits to CallBase and the default case, which
previously relied on not chaining V and going through the Visited set.


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


1 Files Affected:

- (modified) llvm/lib/IR/Value.cpp (+27-14) 


``````````diff
diff --git a/llvm/lib/IR/Value.cpp b/llvm/lib/IR/Value.cpp
index 360bf0f8fc47f..6627a32d35377 100644
--- a/llvm/lib/IR/Value.cpp
+++ b/llvm/lib/IR/Value.cpp
@@ -739,15 +739,20 @@ const Value *Value::stripAndAccumulateConstantOffsets(
          "The offset bit width does not match the DL specification.");
 
   // Even though we don't look through PHI nodes, we could be called on an
-  // instruction in an unreachable block, which may be on a cycle.
-  SmallPtrSet<const Value *, 4> Visited;
-  Visited.insert(this);
+  // instruction in an unreachable block, which may be on a cycle. E.g.:
+  //   %gep = getelementptr inbounds nuw i8, ptr %gep, i64 32
+  //
+  // Bound against that case with a simple iteration counter. The number of
+  // iterations can be quite high for very long GEP chains after loop unrolling.
+  // However, most cases take less than 30 iterations. The rare cases that have
+  // longer chains probably won't benefit from additional information...
   const Value *V = this;
-  do {
+  unsigned N = 0;
+  for (; N < 30; ++N) {
     if (auto *GEP = dyn_cast<GEPOperator>(V)) {
       // If in-bounds was requested, we do not strip non-in-bounds GEPs.
       if (!AllowNonInbounds && !GEP->isInBounds())
-        return V;
+        break;
 
       // If one of the values we have visited is an addrspacecast, then
       // the pointer type of this GEP may be different from the type
@@ -757,13 +762,13 @@ const Value *Value::stripAndAccumulateConstantOffsets(
       // pointer type.
       APInt GEPOffset(DL.getIndexTypeSizeInBits(V->getType()), 0);
       if (!GEP->accumulateConstantOffset(DL, GEPOffset, ExternalAnalysis))
-        return V;
+        break;
 
       // Stop traversal if the pointer offset wouldn't fit in the bit-width
       // provided by the Offset argument. This can happen due to AddrSpaceCast
       // stripping.
       if (GEPOffset.getSignificantBits() > BitWidth)
-        return V;
+        break;
 
       // External Analysis can return a result higher/lower than the value
       // represents. We need to detect overflow/underflow.
@@ -776,10 +781,14 @@ const Value *Value::stripAndAccumulateConstantOffsets(
         Offset = Offset.sadd_ov(GEPOffsetST, Overflow);
         if (Overflow) {
           Offset = std::move(OldOffset);
-          return V;
+          break;
         }
       }
-      V = GEP->getPointerOperand();
+      const Value *NewV = GEP->getPointerOperand();
+      // Quick exit for degenerate IR, which can happen in unreachable blocks.
+      if (NewV == V)
+        break;
+      V = NewV;
     } else if (Operator::getOpcode(V) == Instruction::BitCast ||
                Operator::getOpcode(V) == Instruction::AddrSpaceCast) {
       V = cast<Operator>(V)->getOperand(0);
@@ -789,29 +798,33 @@ const Value *Value::stripAndAccumulateConstantOffsets(
     } else if (const auto *Call = dyn_cast<CallBase>(V)) {
         if (const Value *RV = Call->getReturnedArgOperand())
           V = RV;
-        if (AllowInvariantGroup && Call->isLaunderOrStripInvariantGroup())
+        else if (AllowInvariantGroup && Call->isLaunderOrStripInvariantGroup())
           V = Call->getArgOperand(0);
+        else
+          break;
     } else if (auto *Int2Ptr = dyn_cast<Operator>(V)) {
       // Try to accumulate across (inttoptr (add (ptrtoint p), off)).
       if (!AllowNonInbounds || !LookThroughIntToPtr || !Int2Ptr ||
           Int2Ptr->getOpcode() != Instruction::IntToPtr ||
           Int2Ptr->getOperand(0)->getType()->getScalarSizeInBits() != BitWidth)
-        return V;
+        break;
 
       auto *Add = dyn_cast<AddOperator>(Int2Ptr->getOperand(0));
       if (!Add)
-        return V;
+        break;
 
       auto *Ptr2Int = dyn_cast<PtrToIntOperator>(Add->getOperand(0));
       auto *CI = dyn_cast<ConstantInt>(Add->getOperand(1));
       if (!Ptr2Int || !CI)
-        return V;
+        break;
 
       Offset += CI->getValue();
       V = Ptr2Int->getOperand(0);
+    } else {
+      break;
     }
     assert(V->getType()->isPtrOrPtrVectorTy() && "Unexpected operand type!");
-  } while (Visited.insert(V).second);
+  }
 
   return V;
 }

``````````

</details>


https://github.com/llvm/llvm-project/pull/190473


More information about the llvm-commits mailing list