[llvm] [BasicAA] Extend two-variable MinAbsVarIndex heuristic to different scales (PR #218639)

Antonio Frighetto via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 9 05:06:45 PDT 2026


https://github.com/antoniofrighetto updated https://github.com/llvm/llvm-project/pull/218639

>From 66939c92bf5c3c22eaeaf699ff3c0728a73a97c2 Mon Sep 17 00:00:00 2001
From: Antonio Frighetto <me at antoniofrighetto.com>
Date: Tue, 25 Aug 2026 11:09:37 +0200
Subject: [PATCH 1/3] [BasicAA] Precommit tests (NFC)

---
 .../BasicAA/gep-minabs-different-scale.ll     | 111 ++++++++++++++++++
 1 file changed, 111 insertions(+)
 create mode 100644 llvm/test/Analysis/BasicAA/gep-minabs-different-scale.ll

diff --git a/llvm/test/Analysis/BasicAA/gep-minabs-different-scale.ll b/llvm/test/Analysis/BasicAA/gep-minabs-different-scale.ll
new file mode 100644
index 0000000000000..66098806bc226
--- /dev/null
+++ b/llvm/test/Analysis/BasicAA/gep-minabs-different-scale.ll
@@ -0,0 +1,111 @@
+; RUN: opt -aa-pipeline=basic-aa -passes=aa-eval -print-all-alias-modref-info -disable-output %s 2>&1 | FileCheck %s
+
+; After GEPs decomposition we have: {i, Scale=12} and {j, Scale=18}. After subtraction:
+; VarIdx = 12*i + (-18)*j = 6*(2*i − 3*j), where GCD(12,18)=6, C0=2, C1=3 (C0 even, C1 odd).
+; Thus, the min abs offset equals to 6. As both size accesses are one byte, 6 >= 1 holds.
+define void @noalias_mul_different_scale(ptr %base, i32 %a, i32 %b) {
+; CHECK:       Function: noalias_mul_different_scale
+; CHECK-NEXT:    MayAlias:	i8* %gep1, i8* %gep2
+  %i = or i32 %a, 1
+  %j = or i32 %b, 1
+  %mul.idx.i = mul nsw i32 %i, 12
+  %mul.idx.j = mul nsw i32 %j, 18
+  %gep1 = getelementptr inbounds i8, ptr %base, i32 %mul.idx.i
+  %gep2 = getelementptr inbounds i8, ptr %base, i32 %mul.idx.j
+  load i8, ptr %gep1
+  load i8, ptr %gep2
+  ret void
+}
+
+; A shl on one index doubles its scale. After GEPs decomposition and scale by type size,
+; we have: {i, Scale=4} and {j, Scale=8}.
+; Pointer difference: 4*i − 8*j, where GCD(4,8)=4, C0=1, C1=2 (C0 odd, C1 even).
+; Thus, the min abs offset equals to 4. As both size accesses are one byte, 4 >= 1 holds.
+define void @noalias_shl_different_scale(ptr %base, i32 %a, i32 %b) {
+; CHECK:       Function: noalias_shl_different_scale
+; CHECK-NEXT:    MayAlias:	i8* %gep1, i8* %gep2
+  %i = or i32 %a, 1
+  %j = or i32 %b, 1
+  %shl.idx = shl nsw i32 %j, 1
+  %gep1 = getelementptr inbounds float, ptr %base, i32 %i
+  %gep2 = getelementptr inbounds float, ptr %base, i32 %shl.idx
+  load i8, ptr %gep1
+  load i8, ptr %gep2
+  ret void
+}
+
+; After GEPs decomposition and scale by type size we have: {i, Scale=4} and {j, Scale=8}.
+; Thus, the min abs offset equals to 4. As both size accesses are one byte, 4 >= 1 holds.
+define void @noalias_type_size_different_scale(ptr %base, i32 %a, i32 %b) {
+; CHECK:       Function: noalias_type_size_different_scale
+; CHECK-NEXT:    MayAlias:	i8* %gep1, i8* %gep2
+  %i = or i32 %a, 1
+  %j = or i32 %b, 1
+  %gep1 = getelementptr inbounds float, ptr %base, i32 %i
+  %gep2 = getelementptr inbounds double, ptr %base, i32 %j
+  load i8, ptr %gep1
+  load i8, ptr %gep2
+  ret void
+}
+
+; Same as above, though after decomposition we have: {i, Scale=4, ZExtBits=32} and
+; {j, Scale=8, ZExtBits=32}, thus KnownBits are evaluated accordingly.
+define void @noalias_zext_chain(ptr %base, i32 %a, i32 %b) {
+; CHECK:       Function: noalias_zext_chain
+; CHECK-NEXT:    MayAlias:	i8* %gep1, i8* %gep2
+  %i = or i32 %a, 1
+  %j = or i32 %b, 1
+  %zext.i = zext i32 %i to i64
+  %zext.j = zext i32 %j to i64
+  %gep1 = getelementptr inbounds float, ptr %base, i64 %zext.i
+  %gep2 = getelementptr inbounds double, ptr %base, i64 %zext.j
+  load i8, ptr %gep1
+  load i8, ptr %gep2
+  ret void
+}
+
+; Negative tests.
+
+; Variable offset not a difference, thus C0*V0 + C1*V1 != 0 not implied.
+define void @mayalias_same_effective_sign(ptr %base, i32 %a, i32 %b) {
+; CHECK:       Function: mayalias_same_effective_sign
+; CHECK-NEXT:    MayAlias:	i8* %gep1, i8* %gep2
+  %i = mul nsw i32 %a, -12
+  %j = mul nsw i32 %b, 18
+  %gep1 = getelementptr inbounds i8, ptr %base, i32 %i
+  %gep2 = getelementptr inbounds i8, ptr %base, i32 %j
+  load i8, ptr %gep1
+  load i8, ptr %gep2
+  ret void
+}
+
+; When computing GCD(6,10)=2, we get C0=3 and C1=5. Products are identical,
+; no conflicting bits can be found.
+define void @mayalias_cofactors_not_different(ptr %base, i32 %a, i32 %b) {
+; CHECK:       Function: mayalias_cofactors_not_different
+; CHECK-NEXT:    MayAlias:	i8* %gep1, i8* %gep2
+  %i = or i32 %a, 1
+  %j = or i32 %b, 1
+  %mul.idx.i = mul nsw i32 %i, 6
+  %mul.idx.j = mul nsw i32 %j, 10
+  %gep1 = getelementptr inbounds i8, ptr %base, i32 %mul.idx.i
+  %gep2 = getelementptr inbounds i8, ptr %base, i32 %mul.idx.j
+  load i8, ptr %gep1
+  load i8, ptr %gep2
+  ret void
+}
+
+; Access sizes exceed GCD, may overlap by 2 bytes.
+define void @mayalias_access_wider_than_gcd(ptr %base, i32 %a, i32 %b) {
+; CHECK:       Function: mayalias_access_wider_than_gcd
+; CHECK-NEXT:    MayAlias:	i64* %gep1, i64* %gep2
+  %i = or i32 %a, 1
+  %j = or i32 %b, 1
+  %mul.idx.i = mul nsw i32 %i, 12
+  %mul.idx.j = mul nsw i32 %j, 18
+  %gep1 = getelementptr inbounds i8, ptr %base, i32 %mul.idx.i
+  %gep2 = getelementptr inbounds i8, ptr %base, i32 %mul.idx.j
+  load i64, ptr %gep1
+  load i64, ptr %gep2
+  ret void
+}

>From 3226a975c3520ba3177cdb5fad5740c5b47942ec Mon Sep 17 00:00:00 2001
From: Antonio Frighetto <me at antoniofrighetto.com>
Date: Tue, 25 Aug 2026 11:10:03 +0200
Subject: [PATCH 2/3] [BasicAA] Extend two-variable MinAbsVarIndex heuristic to
 different scales

When performing offset-based reasoning via the min abs heuristic,
the existing two-variables case requires equal absolute scaling
factors. This may be generalized to different scales by factoring
the offset as `GCD*(C0*V0 - C1*V1)`, and relying on cached KnownBits
to prove that the cofactor products differ.

Proofs: https://alive2.llvm.org/ce/z/D4cAVX.
---
 .../llvm/Analysis/BasicAliasAnalysis.h        |  2 +
 llvm/lib/Analysis/BasicAliasAnalysis.cpp      | 66 +++++++++++++++++--
 .../BasicAA/gep-minabs-different-scale.ll     |  8 +--
 3 files changed, 65 insertions(+), 11 deletions(-)

diff --git a/llvm/include/llvm/Analysis/BasicAliasAnalysis.h b/llvm/include/llvm/Analysis/BasicAliasAnalysis.h
index a40676da5b832..5d9288ff2c2f3 100644
--- a/llvm/include/llvm/Analysis/BasicAliasAnalysis.h
+++ b/llvm/include/llvm/Analysis/BasicAliasAnalysis.h
@@ -28,6 +28,7 @@ class DataLayout;
 class DominatorTree;
 class Function;
 class GEPOperator;
+struct KnownBits;
 class PHINode;
 class SelectInst;
 class TargetLibraryInfo;
@@ -127,6 +128,7 @@ class BasicAAResult : public AAResultBase {
   /// VarIndex <= -MinAbsVarIndex || MinAbsVarIndex <= VarIndex, thus
   /// establishing a minimum absolute value of the variable offset.
   std::optional<APInt> computeMinAbsVarOffset(const DecomposedGEP &GEP,
+                                              ArrayRef<KnownBits> VIKnownBits,
                                               DominatorTree *DT,
                                               const AAQueryInfo &AAQI);
 
diff --git a/llvm/lib/Analysis/BasicAliasAnalysis.cpp b/llvm/lib/Analysis/BasicAliasAnalysis.cpp
index 02d644cad3596..5d3a2cfd8a3e5 100644
--- a/llvm/lib/Analysis/BasicAliasAnalysis.cpp
+++ b/llvm/lib/Analysis/BasicAliasAnalysis.cpp
@@ -362,6 +362,18 @@ struct CastedValue {
     return N;
   }
 
+  KnownBits evaluateWith(KnownBits K) const {
+    assert(K.getBitWidth() == V->getType()->getPrimitiveSizeInBits() &&
+           "Incompatible bit width");
+    if (TruncBits)
+      K = K.trunc(K.getBitWidth() - TruncBits);
+    if (SExtBits)
+      K = K.sext(K.getBitWidth() + SExtBits);
+    if (ZExtBits)
+      K = K.zext(K.getBitWidth() + ZExtBits);
+    return K;
+  }
+
   bool canDistributeOver(bool NUW, bool NSW) const {
     // zext(x op<nuw> y) == zext(x) op<nuw> zext(y)
     // sext(x op<nsw> y) == sext(x) op<nsw> sext(y)
@@ -589,6 +601,7 @@ struct BasicAAResult::DecomposedGEP {
 struct BasicAAResult::VariableGEPOffsetInfo {
   APInt GCD;
   ConstantRange OffsetRange;
+  SmallVector<KnownBits, 4> VarIndexKnownBits;
 };
 
 /// If V is a symbolic pointer expression, decompose it into a base pointer
@@ -1303,7 +1316,7 @@ AliasResult BasicAAResult::aliasGEP(
   // Analyze the variable indices, and compute the GCD that the total
   // variable offset is guaranteed to be a multiple of, and its approximate
   // range.
-  auto [GCD, OffsetRange] = analyzeVariableOffsets(DecompGEP1, DT);
+  auto [GCD, OffsetRange, VIKnownBits] = analyzeVariableOffsets(DecompGEP1, DT);
 
   // We now have accesses at two offsets from the same base:
   //  1. (...)*GCD + DecompGEP1.Offset with size V1Size
@@ -1329,7 +1342,8 @@ AliasResult BasicAAResult::aliasGEP(
 
   // If a minimum absolute variable offset can be established, employ it to
   // prove that the two accesses are far enough apart.
-  if (auto MinAbsVarIndex = computeMinAbsVarOffset(DecompGEP1, DT, AAQI)) {
+  if (auto MinAbsVarIndex =
+          computeMinAbsVarOffset(DecompGEP1, VIKnownBits, DT, AAQI)) {
     // The constant offset will have added at least +/-MinAbsVarIndex to it.
     APInt OffsetLo = DecompGEP1.Offset - *MinAbsVarIndex;
     APInt OffsetHi = DecompGEP1.Offset + *MinAbsVarIndex;
@@ -1924,6 +1938,8 @@ BasicAAResult::analyzeVariableOffsets(const DecomposedGEP &GEP,
                                       DominatorTree *DT) {
   APInt GCD;
   ConstantRange OffsetRange(GEP.Offset);
+  SmallVector<KnownBits, 4> VarIndexKnownBits;
+  VarIndexKnownBits.reserve(GEP.VarIndices.size());
 
   for (unsigned I = 0, E = GEP.VarIndices.size(); I != E; ++I) {
     const VariableGEPIndex &Index = GEP.VarIndices[I];
@@ -1931,6 +1947,7 @@ BasicAAResult::analyzeVariableOffsets(const DecomposedGEP &GEP,
 
     SimplifyQuery SQ(DL, DT, &AC, Index.CxtI, /*UseInstrInfo=*/true);
     KnownBits Known = computeKnownBits(Index.Val.V, SQ);
+    VarIndexKnownBits.emplace_back(Known);
 
     APInt ScaleForGCD = Scale;
     if (!Index.IsNSW)
@@ -1973,11 +1990,12 @@ BasicAAResult::analyzeVariableOffsets(const DecomposedGEP &GEP,
       OffsetRange = OffsetRange.add(CR);
   }
 
-  return {GCD, OffsetRange};
+  return {GCD, OffsetRange, std::move(VarIndexKnownBits)};
 }
 
 std::optional<APInt> BasicAAResult::computeMinAbsVarOffset(
-    const DecomposedGEP &GEP, DominatorTree *DT, const AAQueryInfo &AAQI) {
+    const DecomposedGEP &GEP, ArrayRef<KnownBits> VIKnownBits,
+    DominatorTree *DT, const AAQueryInfo &AAQI) {
   // Check if abs(V*Scale) >= abs(Scale) holds in the presence of
   // potentially wrapping math.
   auto MultiplyByScaleNoWrap = [](const VariableGEPIndex &Var) {
@@ -2018,14 +2036,48 @@ std::optional<APInt> BasicAAResult::computeMinAbsVarOffset(
     // inequality of values across loop iterations.
     const VariableGEPIndex &Var0 = VarIndices[0];
     const VariableGEPIndex &Var1 = VarIndices[1];
-    if (Var0.hasNegatedScaleOf(Var1) && Var0.Val.TruncBits == 0 &&
-        Var0.Val.hasSameCastsAs(Var1.Val) && !AAQI.MayBeCrossIteration &&
-        MultiplyByScaleNoWrap(Var0) && MultiplyByScaleNoWrap(Var1) &&
+    bool Preconditions =
+        Var0.Val.TruncBits == 0 && Var0.Val.hasSameCastsAs(Var1.Val) &&
+        !AAQI.MayBeCrossIteration && MultiplyByScaleNoWrap(Var0) &&
+        MultiplyByScaleNoWrap(Var1);
+    if (Var0.hasNegatedScaleOf(Var1) && Preconditions &&
         isKnownNonEqual(Var0.Val.V, Var1.Val.V,
                         SimplifyQuery(DL, DT, &AC, /*CxtI=*/Var0.CxtI
                                                        ? Var0.CxtI
                                                        : Var1.CxtI)))
       return Var0.Scale.abs();
+
+    if (!Preconditions)
+      return std::nullopt;
+
+    // On the chance we have not found a min abs, fallback to the generalization
+    // of the two variables case being handled to different scales:
+    // VarIndex = Scale0*V0 + (-Scale1)*V1 = ScaleGCD*(C0*V0 - C1*V1)
+    // where C0 = abs(Scale0)/ScaleGCD, C1 = abs(Scale1)/ScaleGCD.
+    // If C0*V0 != C1*V1, then abs(VarIndex) >= ScaleGCD, leading to the min
+    // absolute value being ScaleGCD.
+
+    // Ensure scales, after subtraction, have opposite signs.
+    bool EffectiveNeg0 = Var0.IsNegated ^ Var0.Scale.isNegative();
+    bool EffectiveNeg1 = Var1.IsNegated ^ Var1.Scale.isNegative();
+    if (EffectiveNeg0 != EffectiveNeg1) {
+      APInt AbsScale0 = Var0.Scale.abs();
+      APInt AbsScale1 = Var1.Scale.abs();
+      APInt ScaleGCD = APIntOps::GreatestCommonDivisor(AbsScale0, AbsScale1);
+      APInt C0 = AbsScale0.udiv(ScaleGCD);
+      APInt C1 = AbsScale1.udiv(ScaleGCD);
+
+      // Try to check whether C0*V0 and C1*V1 are provably distinct (i.e., one
+      // is guaranteed even while the other is guaranteed odd).
+      auto Known0 = KnownBits::mul(Var0.Val.evaluateWith(VIKnownBits[0]),
+                                   KnownBits::makeConstant(C0));
+
+      auto Known1 = KnownBits::mul(Var1.Val.evaluateWith(VIKnownBits[1]),
+                                   KnownBits::makeConstant(C1));
+
+      if (auto Res = KnownBits::ne(Known0, Known1); Res && *Res)
+        return ScaleGCD;
+    }
   }
 
   return std::nullopt;
diff --git a/llvm/test/Analysis/BasicAA/gep-minabs-different-scale.ll b/llvm/test/Analysis/BasicAA/gep-minabs-different-scale.ll
index 66098806bc226..8e0e99150ba91 100644
--- a/llvm/test/Analysis/BasicAA/gep-minabs-different-scale.ll
+++ b/llvm/test/Analysis/BasicAA/gep-minabs-different-scale.ll
@@ -5,7 +5,7 @@
 ; Thus, the min abs offset equals to 6. As both size accesses are one byte, 6 >= 1 holds.
 define void @noalias_mul_different_scale(ptr %base, i32 %a, i32 %b) {
 ; CHECK:       Function: noalias_mul_different_scale
-; CHECK-NEXT:    MayAlias:	i8* %gep1, i8* %gep2
+; CHECK-NEXT:    NoAlias:	i8* %gep1, i8* %gep2
   %i = or i32 %a, 1
   %j = or i32 %b, 1
   %mul.idx.i = mul nsw i32 %i, 12
@@ -23,7 +23,7 @@ define void @noalias_mul_different_scale(ptr %base, i32 %a, i32 %b) {
 ; Thus, the min abs offset equals to 4. As both size accesses are one byte, 4 >= 1 holds.
 define void @noalias_shl_different_scale(ptr %base, i32 %a, i32 %b) {
 ; CHECK:       Function: noalias_shl_different_scale
-; CHECK-NEXT:    MayAlias:	i8* %gep1, i8* %gep2
+; CHECK-NEXT:    NoAlias:	i8* %gep1, i8* %gep2
   %i = or i32 %a, 1
   %j = or i32 %b, 1
   %shl.idx = shl nsw i32 %j, 1
@@ -38,7 +38,7 @@ define void @noalias_shl_different_scale(ptr %base, i32 %a, i32 %b) {
 ; Thus, the min abs offset equals to 4. As both size accesses are one byte, 4 >= 1 holds.
 define void @noalias_type_size_different_scale(ptr %base, i32 %a, i32 %b) {
 ; CHECK:       Function: noalias_type_size_different_scale
-; CHECK-NEXT:    MayAlias:	i8* %gep1, i8* %gep2
+; CHECK-NEXT:    NoAlias:	i8* %gep1, i8* %gep2
   %i = or i32 %a, 1
   %j = or i32 %b, 1
   %gep1 = getelementptr inbounds float, ptr %base, i32 %i
@@ -52,7 +52,7 @@ define void @noalias_type_size_different_scale(ptr %base, i32 %a, i32 %b) {
 ; {j, Scale=8, ZExtBits=32}, thus KnownBits are evaluated accordingly.
 define void @noalias_zext_chain(ptr %base, i32 %a, i32 %b) {
 ; CHECK:       Function: noalias_zext_chain
-; CHECK-NEXT:    MayAlias:	i8* %gep1, i8* %gep2
+; CHECK-NEXT:    NoAlias:	i8* %gep1, i8* %gep2
   %i = or i32 %a, 1
   %j = or i32 %b, 1
   %zext.i = zext i32 %i to i64

>From d388ce4227832a511a5a281d0102e26348540125 Mon Sep 17 00:00:00 2001
From: Antonio Frighetto <me at antoniofrighetto.com>
Date: Wed, 9 Sep 2026 14:06:13 +0200
Subject: [PATCH 3/3] !fixup early return

---
 llvm/lib/Analysis/BasicAliasAnalysis.cpp | 19 ++++++++++++-------
 1 file changed, 12 insertions(+), 7 deletions(-)

diff --git a/llvm/lib/Analysis/BasicAliasAnalysis.cpp b/llvm/lib/Analysis/BasicAliasAnalysis.cpp
index 5d3a2cfd8a3e5..37fd0688755ab 100644
--- a/llvm/lib/Analysis/BasicAliasAnalysis.cpp
+++ b/llvm/lib/Analysis/BasicAliasAnalysis.cpp
@@ -2040,23 +2040,28 @@ std::optional<APInt> BasicAAResult::computeMinAbsVarOffset(
         Var0.Val.TruncBits == 0 && Var0.Val.hasSameCastsAs(Var1.Val) &&
         !AAQI.MayBeCrossIteration && MultiplyByScaleNoWrap(Var0) &&
         MultiplyByScaleNoWrap(Var1);
-    if (Var0.hasNegatedScaleOf(Var1) && Preconditions &&
-        isKnownNonEqual(Var0.Val.V, Var1.Val.V,
-                        SimplifyQuery(DL, DT, &AC, /*CxtI=*/Var0.CxtI
-                                                       ? Var0.CxtI
-                                                       : Var1.CxtI)))
-      return Var0.Scale.abs();
 
     if (!Preconditions)
       return std::nullopt;
 
+    if (Var0.hasNegatedScaleOf(Var1)) {
+      if (isKnownNonEqual(Var0.Val.V, Var1.Val.V,
+                          SimplifyQuery(DL, DT, &AC, /*CxtI=*/Var0.CxtI
+                                                         ? Var0.CxtI
+                                                         : Var1.CxtI)))
+        return Var0.Scale.abs();
+      // Equal scales would imply the GCD equals the scale itself, leading
+      // the generalized path below not to do better than isKnownNonEqual.
+      return std::nullopt;
+    }
+
     // On the chance we have not found a min abs, fallback to the generalization
     // of the two variables case being handled to different scales:
     // VarIndex = Scale0*V0 + (-Scale1)*V1 = ScaleGCD*(C0*V0 - C1*V1)
     // where C0 = abs(Scale0)/ScaleGCD, C1 = abs(Scale1)/ScaleGCD.
     // If C0*V0 != C1*V1, then abs(VarIndex) >= ScaleGCD, leading to the min
     // absolute value being ScaleGCD.
-
+    //
     // Ensure scales, after subtraction, have opposite signs.
     bool EffectiveNeg0 = Var0.IsNegated ^ Var0.Scale.isNegative();
     bool EffectiveNeg1 = Var1.IsNegated ^ Var1.Scale.isNegative();



More information about the llvm-commits mailing list