Skip to content

Conversation

sebpop
Copy link
Contributor

@sebpop sebpop commented Aug 31, 2025

This patch series removes the use of GEP instructions from delinearization.

  • First two patches improve debug messages for DA and Delin.
  • Third patch adds a way to get the array dimensions info from alloca and global variables.
  • Last patch pulls off the code dependent on GEP.

Testcases adjusted to maintain the patterns they test for.

The third patch can be rewritten (if alloca's and globals are also going to be scheduled for removal from LLVM IR) to send the data from the front-ends attached to assume(true) statements: proof of concept to send data from clang / flang with assumes is still incomplete as it would only send the equivalent of the alloca info, still needs to send info about global vars. This approach would also need some agreement to attach the array_info to the assumes, which changes the LLVM IR.

The current patch does not change the LLVM IR, it assumes the array info can be discovered through inter-procedural analysis, and if IPA is not available, it relies on the other symbolic delinearization techniques.

@llvmbot llvmbot added llvm:analysis Includes value tracking, cost tables and constant folding llvm:transforms labels Aug 31, 2025
@llvmbot
Copy link
Member

llvmbot commented Aug 31, 2025

@llvm/pr-subscribers-llvm-analysis

Author: Sebastian Pop (sebpop)

Changes

This patch series removes the use of GEP instructions from delinearization.

  • First two patches improve debug messages for DA and Delin.
  • Third patch adds a way to get the array dimensions info from alloca and global variables.
  • Last patch pulls off the code dependent on GEP.

Testcases adjusted to maintain the patterns they test for.

The third patch can be rewritten (if alloca's and globals are also going to be scheduled for removal from LLVM IR) to send the data from the front-ends attached to assume(true) statements: proof of concept to send data from clang / flang with assumes is still incomplete as it would only send the equivalent of the alloca info, still needs to send info about global vars. This approach would also need some agreement to attach the array_info to the assumes, which changes the LLVM IR.

The current patch does not change the LLVM IR, it assumes the array info can be discovered through inter-procedural analysis, and if IPA is not available, it relies on the other symbolic delinearization techniques.


Patch is 225.80 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/156215.diff

93 Files Affected:

  • (modified) llvm/include/llvm/Analysis/Delinearization.h (+17-17)
  • (modified) llvm/lib/Analysis/Delinearization.cpp (+261-127)
  • (modified) llvm/lib/Analysis/DependenceAnalysis.cpp (+34-12)
  • (modified) llvm/lib/Analysis/LoopCacheAnalysis.cpp (+2-3)
  • (modified) llvm/test/Analysis/Delinearization/a.ll (+2-4)
  • (added) llvm/test/Analysis/Delinearization/alloca-global-arrays.ll (+208)
  • (added) llvm/test/Analysis/Delinearization/array_info_delinearization.ll (+48)
  • (modified) llvm/test/Analysis/Delinearization/byte_offset.ll (+1-2)
  • (modified) llvm/test/Analysis/Delinearization/constant_functions_multi_dim.ll (+4-8)
  • (modified) llvm/test/Analysis/Delinearization/divide_by_one.ll (+4-8)
  • (modified) llvm/test/Analysis/Delinearization/fixed_size_array.ll (+19-38)
  • (modified) llvm/test/Analysis/Delinearization/gcd_multiply_expr.ll (+28-56)
  • (modified) llvm/test/Analysis/Delinearization/himeno_1.ll (+2-4)
  • (modified) llvm/test/Analysis/Delinearization/himeno_2.ll (+2-4)
  • (modified) llvm/test/Analysis/Delinearization/iv_times_constant_in_subscript.ll (+2-4)
  • (modified) llvm/test/Analysis/Delinearization/multidim_ivs_and_integer_offsets_3d.ll (+2-4)
  • (modified) llvm/test/Analysis/Delinearization/multidim_ivs_and_integer_offsets_nts_3d.ll (+2-4)
  • (modified) llvm/test/Analysis/Delinearization/multidim_ivs_and_parameteric_offsets_3d.ll (+2-4)
  • (modified) llvm/test/Analysis/Delinearization/multidim_only_ivs_2d.ll (+4-8)
  • (modified) llvm/test/Analysis/Delinearization/multidim_only_ivs_2d_nested.ll (+1-2)
  • (modified) llvm/test/Analysis/Delinearization/multidim_only_ivs_3d.ll (+2-4)
  • (modified) llvm/test/Analysis/Delinearization/multidim_only_ivs_3d_cast.ll (+2-4)
  • (modified) llvm/test/Analysis/Delinearization/multidim_two_accesses_different_delinearization.ll (+4-8)
  • (modified) llvm/test/Analysis/Delinearization/parameter_addrec_product.ll (+5-10)
  • (modified) llvm/test/Analysis/Delinearization/terms_with_identity_factor.ll (+4-8)
  • (modified) llvm/test/Analysis/Delinearization/type_mismatch.ll (+1-2)
  • (modified) llvm/test/Analysis/DependenceAnalysis/Coupled.ll (+20-18)
  • (modified) llvm/test/Analysis/DependenceAnalysis/DADelin.ll (+6-6)
  • (modified) llvm/test/Analysis/DependenceAnalysis/DifferentOffsets.ll (+9-2)
  • (modified) llvm/test/Analysis/DependenceAnalysis/FlipFlopBaseAddress.ll (+4-1)
  • (modified) llvm/test/Analysis/DependenceAnalysis/Invariant.ll (+4-1)
  • (modified) llvm/test/Analysis/DependenceAnalysis/PR51512.ll (+1-1)
  • (modified) llvm/test/Analysis/DependenceAnalysis/PreliminaryNoValidityCheckFixedSize.ll (+4-1)
  • (modified) llvm/test/Analysis/DependenceAnalysis/Separability.ll (+10-4)
  • (modified) llvm/test/Analysis/DependenceAnalysis/SimpleSIVNoValidityCheckFixedSize.ll (+16-4)
  • (modified) llvm/test/Analysis/LoopCacheAnalysis/interchange-cost-beneficial.ll (+17-5)
  • (modified) llvm/test/Transforms/LoopInterchange/profitability-vectorization.ll (+2-1)
  • (modified) llvm/test/Transforms/LoopUnrollAndJam/dependencies_multidims.ll (-2)
  • (modified) polly/include/polly/ScopInfo.h (+13)
  • (modified) polly/lib/Analysis/ScopBuilder.cpp (+53-33)
  • (modified) polly/lib/Analysis/ScopInfo.cpp (+32-5)
  • (modified) polly/test/CodeGen/MemAccess/create_arrays.ll (+12-14)
  • (modified) polly/test/CodeGen/MemAccess/create_arrays_heap.ll (+13-14)
  • (modified) polly/test/CodeGen/OpenMP/matmul-parallel.ll (+4-10)
  • (modified) polly/test/CodeGen/invariant-load-dimension.ll (+2-4)
  • (modified) polly/test/DeLICM/load-in-cond-inf-loop.ll (+2-4)
  • (modified) polly/test/ForwardOpTree/atax.ll (+8-11)
  • (modified) polly/test/JSONExporter/ImportArrays/ImportArrays-Mispelled-type.ll (+6-9)
  • (modified) polly/test/JSONExporter/ImportArrays/ImportArrays-Negative-size.ll (+11-11)
  • (modified) polly/test/JSONExporter/ImportArrays/ia4___%bb9---%bb26.jscop.transformed (+2-2)
  • (modified) polly/test/ScheduleOptimizer/GreedyFuse/fuse-double.ll (+5-3)
  • (modified) polly/test/ScheduleOptimizer/GreedyFuse/fuse-inner.ll (+5-3)
  • (modified) polly/test/ScheduleOptimizer/ensure-correct-tile-sizes.ll (+9-6)
  • (modified) polly/test/ScheduleOptimizer/full_partial_tile_separation.ll (+8-5)
  • (modified) polly/test/ScheduleOptimizer/kernel_gemm___%for.body---%for.end24.jscop.transformed (+8-8)
  • (modified) polly/test/ScheduleOptimizer/mat_mul_pattern_data_layout_2.ll (+8-8)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts-after-delicm.ll (+8-9)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts-after-delicm_2.ll (+8-7)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts.ll (+8-6)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_11.ll (+6-5)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_13.ll (+8-7)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_14.ll (+8-7)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_16.ll (+8-6)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_17.ll (+8-6)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_18.ll (+9-6)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_19.ll (+8-6)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_20.ll (+8-7)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_24.ll (+8-6)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_3.ll (+8-7)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_4.ll (+8-7)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_5.ll (+8-8)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_6.ll (+8-9)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_7.ll (+8-7)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_8.ll (+8-8)
  • (modified) polly/test/ScheduleOptimizer/pattern_matching_based_opts_splitmap.ll (+9-8)
  • (modified) polly/test/ScopInfo/assume_gep_bounds.ll (+5-5)
  • (modified) polly/test/ScopInfo/assume_gep_bounds_2.ll (+7-6)
  • (modified) polly/test/ScopInfo/loop_affine_bound_0.ll (+5-5)
  • (modified) polly/test/ScopInfo/loop_affine_bound_1.ll (+7-6)
  • (modified) polly/test/ScopInfo/loop_affine_bound_2.ll (+6-6)
  • (modified) polly/test/ScopInfo/many-scalar-dependences.ll (+6-4)
  • (modified) polly/test/ScopInfo/multidim_fixedsize_different_dimensionality.ll (+17-16)
  • (modified) polly/test/ScopInfo/multidim_fixedsize_multi_offset.ll (+7-6)
  • (modified) polly/test/ScopInfo/multidim_fold_constant_dim_zero.ll (+10-10)
  • (modified) polly/test/ScopInfo/multidim_with_bitcast.ll (+6-6)
  • (modified) polly/test/ScopInfo/non-precise-inv-load-1.ll (-1)
  • (modified) polly/test/ScopInfo/process_added_dimensions.ll (+2-5)
  • (modified) polly/test/ScopInfo/remarks.ll (+7-7)
  • (modified) polly/test/ScopInfo/scalar_to_array.ll (-3)
  • (modified) polly/test/ScopInfo/stmt_with_read_but_without_sideffect.ll (+1-3)
  • (modified) polly/test/ScopInfo/user_provided_non_dominating_assumptions.ll (+5-4)
  • (modified) polly/test/Simplify/gemm.ll (+8-7)
  • (modified) polly/test/Simplify/pr33323.ll (+5-4)
diff --git a/llvm/include/llvm/Analysis/Delinearization.h b/llvm/include/llvm/Analysis/Delinearization.h
index 434cfb61699d6..2eda019abaf46 100644
--- a/llvm/include/llvm/Analysis/Delinearization.h
+++ b/llvm/include/llvm/Analysis/Delinearization.h
@@ -112,6 +112,13 @@ void delinearize(ScalarEvolution &SE, const SCEV *Expr,
                  SmallVectorImpl<const SCEV *> &Subscripts,
                  SmallVectorImpl<const SCEV *> &Sizes, const SCEV *ElementSize);
 
+/// Same as delinearize.  TODO: only use delinearize() instead of other internal
+/// functions.
+bool delinearizeUsingArrayInfo(ScalarEvolution &SE, const SCEV *AccessFn,
+                               SmallVectorImpl<const SCEV *> &Subscripts,
+                               SmallVectorImpl<const SCEV *> &Sizes,
+                               const SCEV *ElementSize);
+
 /// Compute the dimensions of fixed size array from \Expr and save the results
 /// in \p Sizes.
 bool findFixedSizeArrayDimensions(ScalarEvolution &SE, const SCEV *Expr,
@@ -132,28 +139,21 @@ bool findFixedSizeArrayDimensions(ScalarEvolution &SE, const SCEV *Expr,
 /// Different from the normal delinearization, this function assumes that NO
 /// terms exist in the \p Expr. In other words, it assumes that the all step
 /// values are constant.
-///
-/// This function is intended to replace getIndexExpressionsFromGEP and
-/// tryDelinearizeFixedSizeImpl. They rely on the GEP source element type so
-/// that they will be removed in the future.
 bool delinearizeFixedSizeArray(ScalarEvolution &SE, const SCEV *Expr,
                                SmallVectorImpl<const SCEV *> &Subscripts,
                                SmallVectorImpl<const SCEV *> &Sizes,
                                const SCEV *ElementSize);
 
-/// Gathers the individual index expressions from a GEP instruction.
-///
-/// This function optimistically assumes the GEP references into a fixed size
-/// array. If this is actually true, this function returns a list of array
-/// subscript expressions in \p Subscripts and a list of integers describing
-/// the size of the individual array dimensions in \p Sizes. Both lists have
-/// either equal length or the size list is one element shorter in case there
-/// is no known size available for the outermost array dimension. Returns true
-/// if successful and false otherwise.
-bool getIndexExpressionsFromGEP(ScalarEvolution &SE,
-                                const GetElementPtrInst *GEP,
-                                SmallVectorImpl<const SCEV *> &Subscripts,
-                                SmallVectorImpl<int> &Sizes);
+/// Compute access functions for each subscript in a delinearized array access.
+void computeAccessFunctions(ScalarEvolution &SE, const SCEV *Expr,
+                           SmallVectorImpl<const SCEV *> &Subscripts,
+                           SmallVectorImpl<const SCEV *> &Sizes,
+                           const SCEV *ElementSize);
+
+/// Backward compatibility wrapper for computeAccessFunctions.
+void computeAccessFunctions(ScalarEvolution &SE, const SCEV *Expr,
+                           SmallVectorImpl<const SCEV *> &Subscripts,
+                           SmallVectorImpl<const SCEV *> &Sizes);
 
 /// Implementation of fixed size array delinearization. Try to delinearize
 /// access function for a fixed size multi-dimensional array, by deriving
diff --git a/llvm/lib/Analysis/Delinearization.cpp b/llvm/lib/Analysis/Delinearization.cpp
index 762d9191aab1e..873060d5f7f2a 100644
--- a/llvm/lib/Analysis/Delinearization.cpp
+++ b/llvm/lib/Analysis/Delinearization.cpp
@@ -14,15 +14,18 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/Analysis/Delinearization.h"
+#include "llvm/ADT/DenseMap.h"
 #include "llvm/Analysis/LoopInfo.h"
 #include "llvm/Analysis/ScalarEvolution.h"
 #include "llvm/Analysis/ScalarEvolutionDivision.h"
 #include "llvm/Analysis/ScalarEvolutionExpressions.h"
 #include "llvm/IR/Constants.h"
+#include "llvm/IR/DataLayout.h"
 #include "llvm/IR/DerivedTypes.h"
 #include "llvm/IR/Function.h"
 #include "llvm/IR/InstIterator.h"
 #include "llvm/IR/Instructions.h"
+#include "llvm/IR/Module.h"
 #include "llvm/IR/PassManager.h"
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/Debug.h"
@@ -182,7 +185,7 @@ void llvm::collectParametricTerms(ScalarEvolution &SE, const SCEV *Expr,
   LLVM_DEBUG({
     dbgs() << "Strides:\n";
     for (const SCEV *S : Strides)
-      dbgs() << *S << "\n";
+      dbgs() << "  " << *S << "\n";
   });
 
   for (const SCEV *S : Strides) {
@@ -193,7 +196,7 @@ void llvm::collectParametricTerms(ScalarEvolution &SE, const SCEV *Expr,
   LLVM_DEBUG({
     dbgs() << "Terms:\n";
     for (const SCEV *T : Terms)
-      dbgs() << *T << "\n";
+      dbgs() << "  " << *T << "\n";
   });
 
   SCEVCollectAddRecMultiplies MulCollector(Terms, SE);
@@ -294,7 +297,7 @@ void llvm::findArrayDimensions(ScalarEvolution &SE,
   LLVM_DEBUG({
     dbgs() << "Terms:\n";
     for (const SCEV *T : Terms)
-      dbgs() << *T << "\n";
+      dbgs() << "  " << *T << "\n";
   });
 
   // Remove duplicates.
@@ -325,7 +328,7 @@ void llvm::findArrayDimensions(ScalarEvolution &SE,
   LLVM_DEBUG({
     dbgs() << "Terms after sorting:\n";
     for (const SCEV *T : NewTerms)
-      dbgs() << *T << "\n";
+      dbgs() << "  " << *T << "\n";
   });
 
   if (NewTerms.empty() || !findArrayDimensionsRec(SE, NewTerms, Sizes)) {
@@ -339,13 +342,14 @@ void llvm::findArrayDimensions(ScalarEvolution &SE,
   LLVM_DEBUG({
     dbgs() << "Sizes:\n";
     for (const SCEV *S : Sizes)
-      dbgs() << *S << "\n";
+      dbgs() << "  " << *S << "\n";
   });
 }
 
 void llvm::computeAccessFunctions(ScalarEvolution &SE, const SCEV *Expr,
                                   SmallVectorImpl<const SCEV *> &Subscripts,
-                                  SmallVectorImpl<const SCEV *> &Sizes) {
+                                  SmallVectorImpl<const SCEV *> &Sizes,
+                                  const SCEV *ElementSize) {
   // Early exit in case this SCEV is not an affine multivariate function.
   if (Sizes.empty())
     return;
@@ -354,20 +358,32 @@ void llvm::computeAccessFunctions(ScalarEvolution &SE, const SCEV *Expr,
     if (!AR->isAffine())
       return;
 
+  if (ElementSize->isZero())
+    return;
+
+  // Clear output vector.
+  Subscripts.clear();
+
+  LLVM_DEBUG(dbgs() << "\ncomputeAccessFunctions\n"
+                    << "Linearized Memory Access Function: " << *Expr << "\n");
+
   const SCEV *Res = Expr;
   int Last = Sizes.size() - 1;
+
   for (int i = Last; i >= 0; i--) {
-    const SCEV *Q, *R;
-    SCEVDivision::divide(SE, Res, Sizes[i], &Q, &R);
+    const SCEV *Size = Sizes[i];
+    if (Size->isZero())
+      continue;
 
+    const SCEV *Q, *R;
+    SCEVDivision::divide(SE, Res, Size, &Q, &R);
     LLVM_DEBUG({
-      dbgs() << "Res: " << *Res << "\n";
-      dbgs() << "Sizes[i]: " << *Sizes[i] << "\n";
-      dbgs() << "Res divided by Sizes[i]:\n";
-      dbgs() << "Quotient: " << *Q << "\n";
-      dbgs() << "Remainder: " << *R << "\n";
+      dbgs() << "Computing 'MemAccFn / Sizes[" << i << "]':\n";
+      dbgs() << "  MemAccFn: " << *Res << "\n";
+      dbgs() << "  Sizes[" << i << "]: " << *Size << "\n";
+      dbgs() << "  Quotient (Leftover): " << *Q << "\n";
+      dbgs() << "  Remainder (Subscript Access Function): " << *R << "\n";
     });
-
     Res = Q;
 
     // Do not record the last subscript corresponding to the size of elements in
@@ -385,22 +401,162 @@ void llvm::computeAccessFunctions(ScalarEvolution &SE, const SCEV *Expr,
     }
 
     // Record the access function for the current subscript.
+    LLVM_DEBUG(dbgs() << "Subscripts push_back Remainder: " << *R << "\n");
     Subscripts.push_back(R);
   }
 
-  // Also push in last position the remainder of the last division: it will be
-  // the access function of the innermost dimension.
-  Subscripts.push_back(Res);
+  // Also push in last position the quotient "Res = Q" of the last division: it
+  // will be the access function of the outermost array dimension.
+  if (!Res->isZero()) {
+    // This is only needed when the outermost array size is not known.  Res = 0
+    // when the outermost array dimension is known, as for example when reading
+    // array sizes from a local or global declaration.
+    Subscripts.push_back(Res);
+    LLVM_DEBUG(dbgs() << "Subscripts push_back Res: " << *Res << "\n");
+  }
 
   std::reverse(Subscripts.begin(), Subscripts.end());
 
   LLVM_DEBUG({
     dbgs() << "Subscripts:\n";
     for (const SCEV *S : Subscripts)
-      dbgs() << *S << "\n";
+      dbgs() << "  " << *S << "\n";
+    dbgs() << "\n";
   });
 }
 
+/// Backward compatibility wrapper for the old 4-parameter version.
+void llvm::computeAccessFunctions(ScalarEvolution &SE, const SCEV *Expr,
+                                  SmallVectorImpl<const SCEV *> &Subscripts,
+                                  SmallVectorImpl<const SCEV *> &Sizes) {
+  // Use the element size from the last element in Sizes array (legacy behavior)
+  if (Sizes.empty()) {
+    Subscripts.clear();
+    return;
+  }
+  const SCEV *ElementSize = Sizes.back();
+  computeAccessFunctions(SE, Expr, Subscripts, Sizes, ElementSize);
+}
+
+/// Extract array dimensions from alloca or global variable declarations.
+/// Returns true if array dimensions were successfully extracted.
+static bool
+extractArrayInfoFromAllocaOrGlobal(ScalarEvolution &SE, Value *BasePtr,
+                                   SmallVectorImpl<const SCEV *> &Sizes,
+                                   const SCEV *ElementSize) {
+  // Clear output vector.
+  Sizes.clear();
+
+  LLVM_DEBUG(dbgs() << "extractArrayInfoFromAllocaOrGlobal called with BasePtr: " << *BasePtr << "\n");
+
+  // Distinguish between simple array accesses and complex pointer arithmetic.
+  // Only apply array_info extraction to direct array accesses to avoid incorrect
+  // delinearization of complex pointer arithmetic patterns.
+  if (auto *GEP = dyn_cast<GetElementPtrInst>(BasePtr)) {
+    // Check if this is a simple array access pattern: GEP [N x T]* @array, 0, idx
+    // This represents direct indexing like array[i], which should use array dimensions.
+    if (GEP->getNumIndices() == 2) {
+      auto *FirstIdx = dyn_cast<ConstantInt>(GEP->getOperand(1));
+      if (FirstIdx && FirstIdx->isZero()) {
+        // Simple array access: extract dimensions from the underlying array type
+        Value *Source = GEP->getPointerOperand()->stripPointerCasts();
+        return extractArrayInfoFromAllocaOrGlobal(SE, Source, Sizes, ElementSize);
+      }
+    }
+    // Complex GEPs like (&array[offset])[index] represent pointer arithmetic, not
+    // simple array indexing. These should be handled by parametric delinearization
+    // to preserve the linearized byte-offset semantics rather than treating them
+    // as multidimensional array accesses.
+    return false;
+  }
+
+  // Check if BasePtr is from an alloca instruction.
+  Type *ElementType = nullptr;
+  if (auto *AI = dyn_cast<AllocaInst>(BasePtr)) {
+    ElementType = AI->getAllocatedType();
+    LLVM_DEBUG(dbgs() << "Found alloca with type: " << *ElementType << "\n");
+  } else if (auto *GV = dyn_cast<GlobalVariable>(BasePtr)) {
+    ElementType = GV->getValueType();
+    LLVM_DEBUG(dbgs() << "Found global variable with type: " << *ElementType
+                      << "\n");
+  } else {
+    LLVM_DEBUG(dbgs() << "No alloca or global found for base pointer\n");
+    return false;
+  }
+
+  // Extract dimensions from nested array types.
+  Type *I64Ty = Type::getInt64Ty(SE.getContext());
+
+  while (auto *ArrayTy = dyn_cast<ArrayType>(ElementType)) {
+    uint64_t Size = ArrayTy->getNumElements();
+    const SCEV *SizeSCEV = SE.getConstant(I64Ty, Size);
+    Sizes.push_back(SizeSCEV);
+    ElementType = ArrayTy->getElementType();
+    LLVM_DEBUG(dbgs() << "  Found array dimension: " << Size << "\n");
+  }
+
+  if (Sizes.empty()) {
+    LLVM_DEBUG(dbgs() << "No array dimensions found in type\n");
+    return false;
+  }
+
+  // Add element size as the last element for computeAccessFunctions algorithm.
+  Sizes.push_back(ElementSize);
+
+  LLVM_DEBUG({
+    dbgs() << "Extracted array info from alloca/global for base pointer "
+           << *BasePtr << "\n";
+    dbgs() << "Dimensions: ";
+    for (const SCEV *Size : Sizes)
+      dbgs() << *Size << " ";
+    dbgs() << "\n";
+  });
+
+  return true;
+}
+
+bool llvm::delinearizeUsingArrayInfo(ScalarEvolution &SE, const SCEV *AccessFn,
+                                     SmallVectorImpl<const SCEV *> &Subscripts,
+                                     SmallVectorImpl<const SCEV *> &Sizes,
+                                     const SCEV *ElementSize) {
+  // Clear output vectors.
+  Subscripts.clear();
+  Sizes.clear();
+
+  const SCEVUnknown *BasePointer =
+      dyn_cast<SCEVUnknown>(SE.getPointerBase(AccessFn));
+  if (!BasePointer) {
+    LLVM_DEBUG(dbgs() << "no BasePointer for AccessFn: " << *AccessFn << "\n");
+    return false;
+  }
+
+  Value *BasePtr = BasePointer->getValue();
+
+  // Extract array dimensions from alloca or global declarations.
+  if (!extractArrayInfoFromAllocaOrGlobal(SE, BasePtr, Sizes, ElementSize))
+    return false;
+
+  // Get the full SCEV expression and subtract the base pointer to get
+  // offset-only expression.
+  const SCEV *Expr = SE.getMinusSCEV(AccessFn, BasePointer);
+
+  computeAccessFunctions(SE, Expr, Subscripts, Sizes, ElementSize);
+  if (Sizes.empty() || Subscripts.empty())
+    return false;
+
+  // Validate dimension consistency: subscripts should match array dimensions
+  // (Sizes includes element size as last element, so array dimensions = Sizes.size() - 1)
+  unsigned ArrayDims = Sizes.size() - 1;
+  if (Subscripts.size() != ArrayDims) {
+    LLVM_DEBUG(dbgs() << "delinearizeUsingArrayInfo: Dimension mismatch - "
+                      << Subscripts.size() << " subscripts for " << ArrayDims
+                      << " array dimensions. Falling back to parametric method.\n");
+    return false;
+  }
+
+  return true;
+}
+
 /// Splits the SCEV into two vectors of SCEVs representing the subscripts and
 /// sizes of an array access. Returns the remainder of the delinearization that
 /// is the offset start of the array.  The SCEV->delinearize algorithm computes
@@ -454,36 +610,35 @@ void llvm::delinearize(ScalarEvolution &SE, const SCEV *Expr,
                        SmallVectorImpl<const SCEV *> &Subscripts,
                        SmallVectorImpl<const SCEV *> &Sizes,
                        const SCEV *ElementSize) {
-  // First step: collect parametric terms.
+  // Clear output vectors.
+  Subscripts.clear();
+  Sizes.clear();
+
+  // Try array_info extraction.
+  if (delinearizeUsingArrayInfo(SE, Expr, Subscripts, Sizes, ElementSize))
+    return;
+  LLVM_DEBUG(dbgs() << "delinearize falling back to parametric method\n");
+
+  // Fall back to parametric delinearization.
+  const SCEVUnknown *BasePointer =
+      dyn_cast<SCEVUnknown>(SE.getPointerBase(Expr));
+  if (BasePointer)
+    Expr = SE.getMinusSCEV(Expr, BasePointer);
+
   SmallVector<const SCEV *, 4> Terms;
   collectParametricTerms(SE, Expr, Terms);
 
   if (Terms.empty())
     return;
 
-  // Second step: find subscript sizes.
+  // Find subscript sizes.
   findArrayDimensions(SE, Terms, Sizes, ElementSize);
 
   if (Sizes.empty())
     return;
 
-  // Third step: compute the access functions for each subscript.
-  computeAccessFunctions(SE, Expr, Subscripts, Sizes);
-
-  if (Subscripts.empty())
-    return;
-
-  LLVM_DEBUG({
-    dbgs() << "succeeded to delinearize " << *Expr << "\n";
-    dbgs() << "ArrayDecl[UnknownSize]";
-    for (const SCEV *S : Sizes)
-      dbgs() << "[" << *S << "]";
-
-    dbgs() << "\nArrayRef";
-    for (const SCEV *S : Subscripts)
-      dbgs() << "[" << *S << "]";
-    dbgs() << "\n";
-  });
+  // Compute the access functions for each subscript.
+  computeAccessFunctions(SE, Expr, Subscripts, Sizes, ElementSize);
 }
 
 static std::optional<APInt> tryIntoAPInt(const SCEV *S) {
@@ -492,6 +647,21 @@ static std::optional<APInt> tryIntoAPInt(const SCEV *S) {
   return std::nullopt;
 }
 
+/// Convert cached SCEV sizes to int sizes for compatibility.
+/// TODO: Remove this after we remove GEP delinearization.
+static void convertSCEVSizesToIntSizes(ArrayRef<const SCEV *> SCEVSizes,
+                                       SmallVectorImpl<int> &Sizes) {
+  for (const SCEV *S : SCEVSizes) {
+    if (auto *Const = dyn_cast<SCEVConstant>(S)) {
+      const APInt &APVal = Const->getAPInt();
+      if (APVal.isSignedIntN(32)) {
+        int intValue = APVal.getSExtValue();
+        Sizes.push_back(intValue);
+      }
+    }
+  }
+}
+
 /// Collects the absolute values of constant steps for all induction variables.
 /// Returns true if we can prove that all step recurrences are constants and \p
 /// Expr is divisible by \p ElementSize. Each step recurrence is stored in \p
@@ -646,6 +816,9 @@ bool llvm::delinearizeFixedSizeArray(ScalarEvolution &SE, const SCEV *Expr,
                                      SmallVectorImpl<const SCEV *> &Subscripts,
                                      SmallVectorImpl<const SCEV *> &Sizes,
                                      const SCEV *ElementSize) {
+  // Clear output vectors.
+  Subscripts.clear();
+  Sizes.clear();
 
   // First step: find the fixed array size.
   SmallVector<uint64_t, 4> ConstSizes;
@@ -659,52 +832,18 @@ bool llvm::delinearizeFixedSizeArray(ScalarEvolution &SE, const SCEV *Expr,
     Sizes.push_back(SE.getConstant(Expr->getType(), Size));
 
   // Second step: compute the access functions for each subscript.
-  computeAccessFunctions(SE, Expr, Subscripts, Sizes);
+  computeAccessFunctions(SE, Expr, Subscripts, Sizes, ElementSize);
 
   return !Subscripts.empty();
 }
 
-bool llvm::getIndexExpressionsFromGEP(ScalarEvolution &SE,
-                                      const GetElementPtrInst *GEP,
-                                      SmallVectorImpl<const SCEV *> &Subscripts,
-                                      SmallVectorImpl<int> &Sizes) {
-  assert(Subscripts.empty() && Sizes.empty() &&
-         "Expected output lists to be empty on entry to this function.");
-  assert(GEP && "getIndexExpressionsFromGEP called with a null GEP");
-  Type *Ty = nullptr;
-  bool DroppedFirstDim = false;
-  for (unsigned i = 1; i < GEP->getNumOperands(); i++) {
-    const SCEV *Expr = SE.getSCEV(GEP->getOperand(i));
-    if (i == 1) {
-      Ty = GEP->getSourceElementType();
-      if (auto *Const = dyn_cast<SCEVConstant>(Expr))
-        if (Const->getValue()->isZero()) {
-          DroppedFirstDim = true;
-          continue;
-        }
-      Subscripts.push_back(Expr);
-      continue;
-    }
-
-    auto *ArrayTy = dyn_cast<ArrayType>(Ty);
-    if (!ArrayTy) {
-      Subscripts.clear();
-      Sizes.clear();
-      return false;
-    }
-
-    Subscripts.push_back(Expr);
-    if (!(DroppedFirstDim && i == 2))
-      Sizes.push_back(ArrayTy->getNumElements());
-
-    Ty = ArrayTy->getElementType();
-  }
-  return !Subscripts.empty();
-}
-
 bool llvm::tryDelinearizeFixedSizeImpl(
     ScalarEvolution *SE, Instruction *Inst, const SCEV *AccessFn,
     SmallVectorImpl<const SCEV *> &Subscripts, SmallVectorImpl<int> &Sizes) {
+  // Clear output vectors.
+  Subscripts.clear();
+  Sizes.clear();
+
   Value *SrcPtr = getLoadStorePointerOperand(Inst);
 
   // Check the simple case where the array dimensions are fixed size.
@@ -712,31 +851,17 @@ bool llvm::tryDelinearizeFixedSizeImpl(
   if (!SrcGEP)
     return false;
 
-  getIndexExpressionsFromGEP(*SE, SrcGEP, Subscripts, Sizes);
-
-  // Check that the two size arrays are non-empty and equal in length and
-  // value.
-  // TODO: it would be better to let the caller to clear Subscripts, similar
-  // to how we handle Sizes.
-  if (Sizes.empty() || Subscripts.size() <= 1) {
-    Subscripts.clear();
+  SmallVector<const SCEV *, 4> SCEVSizes;
+  const SCEV *ElementSize = SE->getElementSize(Inst);
+  if (!delinearizeUsingArrayInfo(*SE, AccessFn, Subscripts, SCEVSizes,
+                                 ElementSize))
     return false;
-  }
-
-  // Check that for identical base pointers we do not miss index offsets
-  // that have been added before this GEP is applied.
-  Value *SrcBasePtr = SrcGEP->getOperand(0)->stripPointerCasts();
-  const SCEVUnknown *SrcBase =
-      dyn...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Aug 31, 2025

@llvm/pr-subscribers-llvm-transforms

Author: Sebastian Pop (sebpop)

Changes

This patch series removes the use of GEP instructions from delinearization.

  • First two patches improve debug messages for DA and Delin.
  • Third patch adds a way to get the array dimensions info from alloca and global variables.
  • Last patch pulls off the code dependent on GEP.

Testcases adjusted to maintain the patterns they test for.

The third patch can be rewritten (if alloca's and globals are also going to be scheduled for removal from LLVM IR) to send the data from the front-ends attached to assume(true) statements: proof of concept to send data from clang / flang with assumes is still incomplete as it would only send the equivalent of the alloca info, still needs to send info about global vars. This approach would also need some agreement to attach the array_info to the assumes, which changes the LLVM IR.

The current patch does not change the LLVM IR, it assumes the array info can be discovered through inter-procedural analysis, and if IPA is not available, it relies on the other symbolic delinearization techniques.


Patch is 225.80 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/156215.diff

93 Files Affected:

  • (modified) llvm/include/llvm/Analysis/Delinearization.h (+17-17)
  • (modified) llvm/lib/Analysis/Delinearization.cpp (+261-127)
  • (modified) llvm/lib/Analysis/DependenceAnalysis.cpp (+34-12)
  • (modified) llvm/lib/Analysis/LoopCacheAnalysis.cpp (+2-3)
  • (modified) llvm/test/Analysis/Delinearization/a.ll (+2-4)
  • (added) llvm/test/Analysis/Delinearization/alloca-global-arrays.ll (+208)
  • (added) llvm/test/Analysis/Delinearization/array_info_delinearization.ll (+48)
  • (modified) llvm/test/Analysis/Delinearization/byte_offset.ll (+1-2)
  • (modified) llvm/test/Analysis/Delinearization/constant_functions_multi_dim.ll (+4-8)
  • (modified) llvm/test/Analysis/Delinearization/divide_by_one.ll (+4-8)
  • (modified) llvm/test/Analysis/Delinearization/fixed_size_array.ll (+19-38)
  • (modified) llvm/test/Analysis/Delinearization/gcd_multiply_expr.ll (+28-56)
  • (modified) llvm/test/Analysis/Delinearization/himeno_1.ll (+2-4)
  • (modified) llvm/test/Analysis/Delinearization/himeno_2.ll (+2-4)
  • (modified) llvm/test/Analysis/Delinearization/iv_times_constant_in_subscript.ll (+2-4)
  • (modified) llvm/test/Analysis/Delinearization/multidim_ivs_and_integer_offsets_3d.ll (+2-4)
  • (modified) llvm/test/Analysis/Delinearization/multidim_ivs_and_integer_offsets_nts_3d.ll (+2-4)
  • (modified) llvm/test/Analysis/Delinearization/multidim_ivs_and_parameteric_offsets_3d.ll (+2-4)
  • (modified) llvm/test/Analysis/Delinearization/multidim_only_ivs_2d.ll (+4-8)
  • (modified) llvm/test/Analysis/Delinearization/multidim_only_ivs_2d_nested.ll (+1-2)
  • (modified) llvm/test/Analysis/Delinearization/multidim_only_ivs_3d.ll (+2-4)
  • (modified) llvm/test/Analysis/Delinearization/multidim_only_ivs_3d_cast.ll (+2-4)
  • (modified) llvm/test/Analysis/Delinearization/multidim_two_accesses_different_delinearization.ll (+4-8)
  • (modified) llvm/test/Analysis/Delinearization/parameter_addrec_product.ll (+5-10)
  • (modified) llvm/test/Analysis/Delinearization/terms_with_identity_factor.ll (+4-8)
  • (modified) llvm/test/Analysis/Delinearization/type_mismatch.ll (+1-2)
  • (modified) llvm/test/Analysis/DependenceAnalysis/Coupled.ll (+20-18)
  • (modified) llvm/test/Analysis/DependenceAnalysis/DADelin.ll (+6-6)
  • (modified) llvm/test/Analysis/DependenceAnalysis/DifferentOffsets.ll (+9-2)
  • (modified) llvm/test/Analysis/DependenceAnalysis/FlipFlopBaseAddress.ll (+4-1)
  • (modified) llvm/test/Analysis/DependenceAnalysis/Invariant.ll (+4-1)
  • (modified) llvm/test/Analysis/DependenceAnalysis/PR51512.ll (+1-1)
  • (modified) llvm/test/Analysis/DependenceAnalysis/PreliminaryNoValidityCheckFixedSize.ll (+4-1)
  • (modified) llvm/test/Analysis/DependenceAnalysis/Separability.ll (+10-4)
  • (modified) llvm/test/Analysis/DependenceAnalysis/SimpleSIVNoValidityCheckFixedSize.ll (+16-4)
  • (modified) llvm/test/Analysis/LoopCacheAnalysis/interchange-cost-beneficial.ll (+17-5)
  • (modified) llvm/test/Transforms/LoopInterchange/profitability-vectorization.ll (+2-1)
  • (modified) llvm/test/Transforms/LoopUnrollAndJam/dependencies_multidims.ll (-2)
  • (modified) polly/include/polly/ScopInfo.h (+13)
  • (modified) polly/lib/Analysis/ScopBuilder.cpp (+53-33)
  • (modified) polly/lib/Analysis/ScopInfo.cpp (+32-5)
  • (modified) polly/test/CodeGen/MemAccess/create_arrays.ll (+12-14)
  • (modified) polly/test/CodeGen/MemAccess/create_arrays_heap.ll (+13-14)
  • (modified) polly/test/CodeGen/OpenMP/matmul-parallel.ll (+4-10)
  • (modified) polly/test/CodeGen/invariant-load-dimension.ll (+2-4)
  • (modified) polly/test/DeLICM/load-in-cond-inf-loop.ll (+2-4)
  • (modified) polly/test/ForwardOpTree/atax.ll (+8-11)
  • (modified) polly/test/JSONExporter/ImportArrays/ImportArrays-Mispelled-type.ll (+6-9)
  • (modified) polly/test/JSONExporter/ImportArrays/ImportArrays-Negative-size.ll (+11-11)
  • (modified) polly/test/JSONExporter/ImportArrays/ia4___%bb9---%bb26.jscop.transformed (+2-2)
  • (modified) polly/test/ScheduleOptimizer/GreedyFuse/fuse-double.ll (+5-3)
  • (modified) polly/test/ScheduleOptimizer/GreedyFuse/fuse-inner.ll (+5-3)
  • (modified) polly/test/ScheduleOptimizer/ensure-correct-tile-sizes.ll (+9-6)
  • (modified) polly/test/ScheduleOptimizer/full_partial_tile_separation.ll (+8-5)
  • (modified) polly/test/ScheduleOptimizer/kernel_gemm___%for.body---%for.end24.jscop.transformed (+8-8)
  • (modified) polly/test/ScheduleOptimizer/mat_mul_pattern_data_layout_2.ll (+8-8)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts-after-delicm.ll (+8-9)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts-after-delicm_2.ll (+8-7)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts.ll (+8-6)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_11.ll (+6-5)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_13.ll (+8-7)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_14.ll (+8-7)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_16.ll (+8-6)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_17.ll (+8-6)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_18.ll (+9-6)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_19.ll (+8-6)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_20.ll (+8-7)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_24.ll (+8-6)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_3.ll (+8-7)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_4.ll (+8-7)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_5.ll (+8-8)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_6.ll (+8-9)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_7.ll (+8-7)
  • (modified) polly/test/ScheduleOptimizer/pattern-matching-based-opts_8.ll (+8-8)
  • (modified) polly/test/ScheduleOptimizer/pattern_matching_based_opts_splitmap.ll (+9-8)
  • (modified) polly/test/ScopInfo/assume_gep_bounds.ll (+5-5)
  • (modified) polly/test/ScopInfo/assume_gep_bounds_2.ll (+7-6)
  • (modified) polly/test/ScopInfo/loop_affine_bound_0.ll (+5-5)
  • (modified) polly/test/ScopInfo/loop_affine_bound_1.ll (+7-6)
  • (modified) polly/test/ScopInfo/loop_affine_bound_2.ll (+6-6)
  • (modified) polly/test/ScopInfo/many-scalar-dependences.ll (+6-4)
  • (modified) polly/test/ScopInfo/multidim_fixedsize_different_dimensionality.ll (+17-16)
  • (modified) polly/test/ScopInfo/multidim_fixedsize_multi_offset.ll (+7-6)
  • (modified) polly/test/ScopInfo/multidim_fold_constant_dim_zero.ll (+10-10)
  • (modified) polly/test/ScopInfo/multidim_with_bitcast.ll (+6-6)
  • (modified) polly/test/ScopInfo/non-precise-inv-load-1.ll (-1)
  • (modified) polly/test/ScopInfo/process_added_dimensions.ll (+2-5)
  • (modified) polly/test/ScopInfo/remarks.ll (+7-7)
  • (modified) polly/test/ScopInfo/scalar_to_array.ll (-3)
  • (modified) polly/test/ScopInfo/stmt_with_read_but_without_sideffect.ll (+1-3)
  • (modified) polly/test/ScopInfo/user_provided_non_dominating_assumptions.ll (+5-4)
  • (modified) polly/test/Simplify/gemm.ll (+8-7)
  • (modified) polly/test/Simplify/pr33323.ll (+5-4)
diff --git a/llvm/include/llvm/Analysis/Delinearization.h b/llvm/include/llvm/Analysis/Delinearization.h
index 434cfb61699d6..2eda019abaf46 100644
--- a/llvm/include/llvm/Analysis/Delinearization.h
+++ b/llvm/include/llvm/Analysis/Delinearization.h
@@ -112,6 +112,13 @@ void delinearize(ScalarEvolution &SE, const SCEV *Expr,
                  SmallVectorImpl<const SCEV *> &Subscripts,
                  SmallVectorImpl<const SCEV *> &Sizes, const SCEV *ElementSize);
 
+/// Same as delinearize.  TODO: only use delinearize() instead of other internal
+/// functions.
+bool delinearizeUsingArrayInfo(ScalarEvolution &SE, const SCEV *AccessFn,
+                               SmallVectorImpl<const SCEV *> &Subscripts,
+                               SmallVectorImpl<const SCEV *> &Sizes,
+                               const SCEV *ElementSize);
+
 /// Compute the dimensions of fixed size array from \Expr and save the results
 /// in \p Sizes.
 bool findFixedSizeArrayDimensions(ScalarEvolution &SE, const SCEV *Expr,
@@ -132,28 +139,21 @@ bool findFixedSizeArrayDimensions(ScalarEvolution &SE, const SCEV *Expr,
 /// Different from the normal delinearization, this function assumes that NO
 /// terms exist in the \p Expr. In other words, it assumes that the all step
 /// values are constant.
-///
-/// This function is intended to replace getIndexExpressionsFromGEP and
-/// tryDelinearizeFixedSizeImpl. They rely on the GEP source element type so
-/// that they will be removed in the future.
 bool delinearizeFixedSizeArray(ScalarEvolution &SE, const SCEV *Expr,
                                SmallVectorImpl<const SCEV *> &Subscripts,
                                SmallVectorImpl<const SCEV *> &Sizes,
                                const SCEV *ElementSize);
 
-/// Gathers the individual index expressions from a GEP instruction.
-///
-/// This function optimistically assumes the GEP references into a fixed size
-/// array. If this is actually true, this function returns a list of array
-/// subscript expressions in \p Subscripts and a list of integers describing
-/// the size of the individual array dimensions in \p Sizes. Both lists have
-/// either equal length or the size list is one element shorter in case there
-/// is no known size available for the outermost array dimension. Returns true
-/// if successful and false otherwise.
-bool getIndexExpressionsFromGEP(ScalarEvolution &SE,
-                                const GetElementPtrInst *GEP,
-                                SmallVectorImpl<const SCEV *> &Subscripts,
-                                SmallVectorImpl<int> &Sizes);
+/// Compute access functions for each subscript in a delinearized array access.
+void computeAccessFunctions(ScalarEvolution &SE, const SCEV *Expr,
+                           SmallVectorImpl<const SCEV *> &Subscripts,
+                           SmallVectorImpl<const SCEV *> &Sizes,
+                           const SCEV *ElementSize);
+
+/// Backward compatibility wrapper for computeAccessFunctions.
+void computeAccessFunctions(ScalarEvolution &SE, const SCEV *Expr,
+                           SmallVectorImpl<const SCEV *> &Subscripts,
+                           SmallVectorImpl<const SCEV *> &Sizes);
 
 /// Implementation of fixed size array delinearization. Try to delinearize
 /// access function for a fixed size multi-dimensional array, by deriving
diff --git a/llvm/lib/Analysis/Delinearization.cpp b/llvm/lib/Analysis/Delinearization.cpp
index 762d9191aab1e..873060d5f7f2a 100644
--- a/llvm/lib/Analysis/Delinearization.cpp
+++ b/llvm/lib/Analysis/Delinearization.cpp
@@ -14,15 +14,18 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/Analysis/Delinearization.h"
+#include "llvm/ADT/DenseMap.h"
 #include "llvm/Analysis/LoopInfo.h"
 #include "llvm/Analysis/ScalarEvolution.h"
 #include "llvm/Analysis/ScalarEvolutionDivision.h"
 #include "llvm/Analysis/ScalarEvolutionExpressions.h"
 #include "llvm/IR/Constants.h"
+#include "llvm/IR/DataLayout.h"
 #include "llvm/IR/DerivedTypes.h"
 #include "llvm/IR/Function.h"
 #include "llvm/IR/InstIterator.h"
 #include "llvm/IR/Instructions.h"
+#include "llvm/IR/Module.h"
 #include "llvm/IR/PassManager.h"
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/Debug.h"
@@ -182,7 +185,7 @@ void llvm::collectParametricTerms(ScalarEvolution &SE, const SCEV *Expr,
   LLVM_DEBUG({
     dbgs() << "Strides:\n";
     for (const SCEV *S : Strides)
-      dbgs() << *S << "\n";
+      dbgs() << "  " << *S << "\n";
   });
 
   for (const SCEV *S : Strides) {
@@ -193,7 +196,7 @@ void llvm::collectParametricTerms(ScalarEvolution &SE, const SCEV *Expr,
   LLVM_DEBUG({
     dbgs() << "Terms:\n";
     for (const SCEV *T : Terms)
-      dbgs() << *T << "\n";
+      dbgs() << "  " << *T << "\n";
   });
 
   SCEVCollectAddRecMultiplies MulCollector(Terms, SE);
@@ -294,7 +297,7 @@ void llvm::findArrayDimensions(ScalarEvolution &SE,
   LLVM_DEBUG({
     dbgs() << "Terms:\n";
     for (const SCEV *T : Terms)
-      dbgs() << *T << "\n";
+      dbgs() << "  " << *T << "\n";
   });
 
   // Remove duplicates.
@@ -325,7 +328,7 @@ void llvm::findArrayDimensions(ScalarEvolution &SE,
   LLVM_DEBUG({
     dbgs() << "Terms after sorting:\n";
     for (const SCEV *T : NewTerms)
-      dbgs() << *T << "\n";
+      dbgs() << "  " << *T << "\n";
   });
 
   if (NewTerms.empty() || !findArrayDimensionsRec(SE, NewTerms, Sizes)) {
@@ -339,13 +342,14 @@ void llvm::findArrayDimensions(ScalarEvolution &SE,
   LLVM_DEBUG({
     dbgs() << "Sizes:\n";
     for (const SCEV *S : Sizes)
-      dbgs() << *S << "\n";
+      dbgs() << "  " << *S << "\n";
   });
 }
 
 void llvm::computeAccessFunctions(ScalarEvolution &SE, const SCEV *Expr,
                                   SmallVectorImpl<const SCEV *> &Subscripts,
-                                  SmallVectorImpl<const SCEV *> &Sizes) {
+                                  SmallVectorImpl<const SCEV *> &Sizes,
+                                  const SCEV *ElementSize) {
   // Early exit in case this SCEV is not an affine multivariate function.
   if (Sizes.empty())
     return;
@@ -354,20 +358,32 @@ void llvm::computeAccessFunctions(ScalarEvolution &SE, const SCEV *Expr,
     if (!AR->isAffine())
       return;
 
+  if (ElementSize->isZero())
+    return;
+
+  // Clear output vector.
+  Subscripts.clear();
+
+  LLVM_DEBUG(dbgs() << "\ncomputeAccessFunctions\n"
+                    << "Linearized Memory Access Function: " << *Expr << "\n");
+
   const SCEV *Res = Expr;
   int Last = Sizes.size() - 1;
+
   for (int i = Last; i >= 0; i--) {
-    const SCEV *Q, *R;
-    SCEVDivision::divide(SE, Res, Sizes[i], &Q, &R);
+    const SCEV *Size = Sizes[i];
+    if (Size->isZero())
+      continue;
 
+    const SCEV *Q, *R;
+    SCEVDivision::divide(SE, Res, Size, &Q, &R);
     LLVM_DEBUG({
-      dbgs() << "Res: " << *Res << "\n";
-      dbgs() << "Sizes[i]: " << *Sizes[i] << "\n";
-      dbgs() << "Res divided by Sizes[i]:\n";
-      dbgs() << "Quotient: " << *Q << "\n";
-      dbgs() << "Remainder: " << *R << "\n";
+      dbgs() << "Computing 'MemAccFn / Sizes[" << i << "]':\n";
+      dbgs() << "  MemAccFn: " << *Res << "\n";
+      dbgs() << "  Sizes[" << i << "]: " << *Size << "\n";
+      dbgs() << "  Quotient (Leftover): " << *Q << "\n";
+      dbgs() << "  Remainder (Subscript Access Function): " << *R << "\n";
     });
-
     Res = Q;
 
     // Do not record the last subscript corresponding to the size of elements in
@@ -385,22 +401,162 @@ void llvm::computeAccessFunctions(ScalarEvolution &SE, const SCEV *Expr,
     }
 
     // Record the access function for the current subscript.
+    LLVM_DEBUG(dbgs() << "Subscripts push_back Remainder: " << *R << "\n");
     Subscripts.push_back(R);
   }
 
-  // Also push in last position the remainder of the last division: it will be
-  // the access function of the innermost dimension.
-  Subscripts.push_back(Res);
+  // Also push in last position the quotient "Res = Q" of the last division: it
+  // will be the access function of the outermost array dimension.
+  if (!Res->isZero()) {
+    // This is only needed when the outermost array size is not known.  Res = 0
+    // when the outermost array dimension is known, as for example when reading
+    // array sizes from a local or global declaration.
+    Subscripts.push_back(Res);
+    LLVM_DEBUG(dbgs() << "Subscripts push_back Res: " << *Res << "\n");
+  }
 
   std::reverse(Subscripts.begin(), Subscripts.end());
 
   LLVM_DEBUG({
     dbgs() << "Subscripts:\n";
     for (const SCEV *S : Subscripts)
-      dbgs() << *S << "\n";
+      dbgs() << "  " << *S << "\n";
+    dbgs() << "\n";
   });
 }
 
+/// Backward compatibility wrapper for the old 4-parameter version.
+void llvm::computeAccessFunctions(ScalarEvolution &SE, const SCEV *Expr,
+                                  SmallVectorImpl<const SCEV *> &Subscripts,
+                                  SmallVectorImpl<const SCEV *> &Sizes) {
+  // Use the element size from the last element in Sizes array (legacy behavior)
+  if (Sizes.empty()) {
+    Subscripts.clear();
+    return;
+  }
+  const SCEV *ElementSize = Sizes.back();
+  computeAccessFunctions(SE, Expr, Subscripts, Sizes, ElementSize);
+}
+
+/// Extract array dimensions from alloca or global variable declarations.
+/// Returns true if array dimensions were successfully extracted.
+static bool
+extractArrayInfoFromAllocaOrGlobal(ScalarEvolution &SE, Value *BasePtr,
+                                   SmallVectorImpl<const SCEV *> &Sizes,
+                                   const SCEV *ElementSize) {
+  // Clear output vector.
+  Sizes.clear();
+
+  LLVM_DEBUG(dbgs() << "extractArrayInfoFromAllocaOrGlobal called with BasePtr: " << *BasePtr << "\n");
+
+  // Distinguish between simple array accesses and complex pointer arithmetic.
+  // Only apply array_info extraction to direct array accesses to avoid incorrect
+  // delinearization of complex pointer arithmetic patterns.
+  if (auto *GEP = dyn_cast<GetElementPtrInst>(BasePtr)) {
+    // Check if this is a simple array access pattern: GEP [N x T]* @array, 0, idx
+    // This represents direct indexing like array[i], which should use array dimensions.
+    if (GEP->getNumIndices() == 2) {
+      auto *FirstIdx = dyn_cast<ConstantInt>(GEP->getOperand(1));
+      if (FirstIdx && FirstIdx->isZero()) {
+        // Simple array access: extract dimensions from the underlying array type
+        Value *Source = GEP->getPointerOperand()->stripPointerCasts();
+        return extractArrayInfoFromAllocaOrGlobal(SE, Source, Sizes, ElementSize);
+      }
+    }
+    // Complex GEPs like (&array[offset])[index] represent pointer arithmetic, not
+    // simple array indexing. These should be handled by parametric delinearization
+    // to preserve the linearized byte-offset semantics rather than treating them
+    // as multidimensional array accesses.
+    return false;
+  }
+
+  // Check if BasePtr is from an alloca instruction.
+  Type *ElementType = nullptr;
+  if (auto *AI = dyn_cast<AllocaInst>(BasePtr)) {
+    ElementType = AI->getAllocatedType();
+    LLVM_DEBUG(dbgs() << "Found alloca with type: " << *ElementType << "\n");
+  } else if (auto *GV = dyn_cast<GlobalVariable>(BasePtr)) {
+    ElementType = GV->getValueType();
+    LLVM_DEBUG(dbgs() << "Found global variable with type: " << *ElementType
+                      << "\n");
+  } else {
+    LLVM_DEBUG(dbgs() << "No alloca or global found for base pointer\n");
+    return false;
+  }
+
+  // Extract dimensions from nested array types.
+  Type *I64Ty = Type::getInt64Ty(SE.getContext());
+
+  while (auto *ArrayTy = dyn_cast<ArrayType>(ElementType)) {
+    uint64_t Size = ArrayTy->getNumElements();
+    const SCEV *SizeSCEV = SE.getConstant(I64Ty, Size);
+    Sizes.push_back(SizeSCEV);
+    ElementType = ArrayTy->getElementType();
+    LLVM_DEBUG(dbgs() << "  Found array dimension: " << Size << "\n");
+  }
+
+  if (Sizes.empty()) {
+    LLVM_DEBUG(dbgs() << "No array dimensions found in type\n");
+    return false;
+  }
+
+  // Add element size as the last element for computeAccessFunctions algorithm.
+  Sizes.push_back(ElementSize);
+
+  LLVM_DEBUG({
+    dbgs() << "Extracted array info from alloca/global for base pointer "
+           << *BasePtr << "\n";
+    dbgs() << "Dimensions: ";
+    for (const SCEV *Size : Sizes)
+      dbgs() << *Size << " ";
+    dbgs() << "\n";
+  });
+
+  return true;
+}
+
+bool llvm::delinearizeUsingArrayInfo(ScalarEvolution &SE, const SCEV *AccessFn,
+                                     SmallVectorImpl<const SCEV *> &Subscripts,
+                                     SmallVectorImpl<const SCEV *> &Sizes,
+                                     const SCEV *ElementSize) {
+  // Clear output vectors.
+  Subscripts.clear();
+  Sizes.clear();
+
+  const SCEVUnknown *BasePointer =
+      dyn_cast<SCEVUnknown>(SE.getPointerBase(AccessFn));
+  if (!BasePointer) {
+    LLVM_DEBUG(dbgs() << "no BasePointer for AccessFn: " << *AccessFn << "\n");
+    return false;
+  }
+
+  Value *BasePtr = BasePointer->getValue();
+
+  // Extract array dimensions from alloca or global declarations.
+  if (!extractArrayInfoFromAllocaOrGlobal(SE, BasePtr, Sizes, ElementSize))
+    return false;
+
+  // Get the full SCEV expression and subtract the base pointer to get
+  // offset-only expression.
+  const SCEV *Expr = SE.getMinusSCEV(AccessFn, BasePointer);
+
+  computeAccessFunctions(SE, Expr, Subscripts, Sizes, ElementSize);
+  if (Sizes.empty() || Subscripts.empty())
+    return false;
+
+  // Validate dimension consistency: subscripts should match array dimensions
+  // (Sizes includes element size as last element, so array dimensions = Sizes.size() - 1)
+  unsigned ArrayDims = Sizes.size() - 1;
+  if (Subscripts.size() != ArrayDims) {
+    LLVM_DEBUG(dbgs() << "delinearizeUsingArrayInfo: Dimension mismatch - "
+                      << Subscripts.size() << " subscripts for " << ArrayDims
+                      << " array dimensions. Falling back to parametric method.\n");
+    return false;
+  }
+
+  return true;
+}
+
 /// Splits the SCEV into two vectors of SCEVs representing the subscripts and
 /// sizes of an array access. Returns the remainder of the delinearization that
 /// is the offset start of the array.  The SCEV->delinearize algorithm computes
@@ -454,36 +610,35 @@ void llvm::delinearize(ScalarEvolution &SE, const SCEV *Expr,
                        SmallVectorImpl<const SCEV *> &Subscripts,
                        SmallVectorImpl<const SCEV *> &Sizes,
                        const SCEV *ElementSize) {
-  // First step: collect parametric terms.
+  // Clear output vectors.
+  Subscripts.clear();
+  Sizes.clear();
+
+  // Try array_info extraction.
+  if (delinearizeUsingArrayInfo(SE, Expr, Subscripts, Sizes, ElementSize))
+    return;
+  LLVM_DEBUG(dbgs() << "delinearize falling back to parametric method\n");
+
+  // Fall back to parametric delinearization.
+  const SCEVUnknown *BasePointer =
+      dyn_cast<SCEVUnknown>(SE.getPointerBase(Expr));
+  if (BasePointer)
+    Expr = SE.getMinusSCEV(Expr, BasePointer);
+
   SmallVector<const SCEV *, 4> Terms;
   collectParametricTerms(SE, Expr, Terms);
 
   if (Terms.empty())
     return;
 
-  // Second step: find subscript sizes.
+  // Find subscript sizes.
   findArrayDimensions(SE, Terms, Sizes, ElementSize);
 
   if (Sizes.empty())
     return;
 
-  // Third step: compute the access functions for each subscript.
-  computeAccessFunctions(SE, Expr, Subscripts, Sizes);
-
-  if (Subscripts.empty())
-    return;
-
-  LLVM_DEBUG({
-    dbgs() << "succeeded to delinearize " << *Expr << "\n";
-    dbgs() << "ArrayDecl[UnknownSize]";
-    for (const SCEV *S : Sizes)
-      dbgs() << "[" << *S << "]";
-
-    dbgs() << "\nArrayRef";
-    for (const SCEV *S : Subscripts)
-      dbgs() << "[" << *S << "]";
-    dbgs() << "\n";
-  });
+  // Compute the access functions for each subscript.
+  computeAccessFunctions(SE, Expr, Subscripts, Sizes, ElementSize);
 }
 
 static std::optional<APInt> tryIntoAPInt(const SCEV *S) {
@@ -492,6 +647,21 @@ static std::optional<APInt> tryIntoAPInt(const SCEV *S) {
   return std::nullopt;
 }
 
+/// Convert cached SCEV sizes to int sizes for compatibility.
+/// TODO: Remove this after we remove GEP delinearization.
+static void convertSCEVSizesToIntSizes(ArrayRef<const SCEV *> SCEVSizes,
+                                       SmallVectorImpl<int> &Sizes) {
+  for (const SCEV *S : SCEVSizes) {
+    if (auto *Const = dyn_cast<SCEVConstant>(S)) {
+      const APInt &APVal = Const->getAPInt();
+      if (APVal.isSignedIntN(32)) {
+        int intValue = APVal.getSExtValue();
+        Sizes.push_back(intValue);
+      }
+    }
+  }
+}
+
 /// Collects the absolute values of constant steps for all induction variables.
 /// Returns true if we can prove that all step recurrences are constants and \p
 /// Expr is divisible by \p ElementSize. Each step recurrence is stored in \p
@@ -646,6 +816,9 @@ bool llvm::delinearizeFixedSizeArray(ScalarEvolution &SE, const SCEV *Expr,
                                      SmallVectorImpl<const SCEV *> &Subscripts,
                                      SmallVectorImpl<const SCEV *> &Sizes,
                                      const SCEV *ElementSize) {
+  // Clear output vectors.
+  Subscripts.clear();
+  Sizes.clear();
 
   // First step: find the fixed array size.
   SmallVector<uint64_t, 4> ConstSizes;
@@ -659,52 +832,18 @@ bool llvm::delinearizeFixedSizeArray(ScalarEvolution &SE, const SCEV *Expr,
     Sizes.push_back(SE.getConstant(Expr->getType(), Size));
 
   // Second step: compute the access functions for each subscript.
-  computeAccessFunctions(SE, Expr, Subscripts, Sizes);
+  computeAccessFunctions(SE, Expr, Subscripts, Sizes, ElementSize);
 
   return !Subscripts.empty();
 }
 
-bool llvm::getIndexExpressionsFromGEP(ScalarEvolution &SE,
-                                      const GetElementPtrInst *GEP,
-                                      SmallVectorImpl<const SCEV *> &Subscripts,
-                                      SmallVectorImpl<int> &Sizes) {
-  assert(Subscripts.empty() && Sizes.empty() &&
-         "Expected output lists to be empty on entry to this function.");
-  assert(GEP && "getIndexExpressionsFromGEP called with a null GEP");
-  Type *Ty = nullptr;
-  bool DroppedFirstDim = false;
-  for (unsigned i = 1; i < GEP->getNumOperands(); i++) {
-    const SCEV *Expr = SE.getSCEV(GEP->getOperand(i));
-    if (i == 1) {
-      Ty = GEP->getSourceElementType();
-      if (auto *Const = dyn_cast<SCEVConstant>(Expr))
-        if (Const->getValue()->isZero()) {
-          DroppedFirstDim = true;
-          continue;
-        }
-      Subscripts.push_back(Expr);
-      continue;
-    }
-
-    auto *ArrayTy = dyn_cast<ArrayType>(Ty);
-    if (!ArrayTy) {
-      Subscripts.clear();
-      Sizes.clear();
-      return false;
-    }
-
-    Subscripts.push_back(Expr);
-    if (!(DroppedFirstDim && i == 2))
-      Sizes.push_back(ArrayTy->getNumElements());
-
-    Ty = ArrayTy->getElementType();
-  }
-  return !Subscripts.empty();
-}
-
 bool llvm::tryDelinearizeFixedSizeImpl(
     ScalarEvolution *SE, Instruction *Inst, const SCEV *AccessFn,
     SmallVectorImpl<const SCEV *> &Subscripts, SmallVectorImpl<int> &Sizes) {
+  // Clear output vectors.
+  Subscripts.clear();
+  Sizes.clear();
+
   Value *SrcPtr = getLoadStorePointerOperand(Inst);
 
   // Check the simple case where the array dimensions are fixed size.
@@ -712,31 +851,17 @@ bool llvm::tryDelinearizeFixedSizeImpl(
   if (!SrcGEP)
     return false;
 
-  getIndexExpressionsFromGEP(*SE, SrcGEP, Subscripts, Sizes);
-
-  // Check that the two size arrays are non-empty and equal in length and
-  // value.
-  // TODO: it would be better to let the caller to clear Subscripts, similar
-  // to how we handle Sizes.
-  if (Sizes.empty() || Subscripts.size() <= 1) {
-    Subscripts.clear();
+  SmallVector<const SCEV *, 4> SCEVSizes;
+  const SCEV *ElementSize = SE->getElementSize(Inst);
+  if (!delinearizeUsingArrayInfo(*SE, AccessFn, Subscripts, SCEVSizes,
+                                 ElementSize))
     return false;
-  }
-
-  // Check that for identical base pointers we do not miss index offsets
-  // that have been added before this GEP is applied.
-  Value *SrcBasePtr = SrcGEP->getOperand(0)->stripPointerCasts();
-  const SCEVUnknown *SrcBase =
-      dyn...
[truncated]

sebpop added 2 commits August 30, 2025 19:04
…tions

This implements an alternative to extract multi-dimensional array information
directly from alloca instructions and global variable declarations instead of
relying on parsing the information from GEP instructions.

The functionality for GEP delinearization remains in this patch such that we can
evaluate regressions of the array_info extraction.

Next patch removes the code for GEP delinearization.
This finishes removing the code of GEP delinearization.

Follow up patches will clean the interface, in particular Sizes array of ints
will be rewritten as an array of SCEVs to avoid unnecessary translations.

Another painful point to be addressed in subsequent patches is unifying the
delinearize() interface to avoid users directly hit in the internals of
delinearize. The many uses of delinearization internals makes changes to the
core delinearization algorithm hard to modify.
Copy link

github-actions bot commented Aug 31, 2025

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

@sebpop sebpop force-pushed the delin-GEP-removal branch from 6b1020d to 1f39c61 Compare August 31, 2025 00:06
@sjoerdmeijer
Copy link
Collaborator

It's good to see the flow from beginning to end, but I think we want to comment on the patches individually and merge them separately, so I think each change needs its own merge request.

@sebpop
Copy link
Contributor Author

sebpop commented Sep 1, 2025

#156339
#156342

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

Labels

llvm:analysis Includes value tracking, cost tables and constant folding llvm:transforms

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants