[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