[llvm] [BasicAA] Extend two-variable MinAbsVarIndex heuristic to different scales (PR #218639)
via llvm-commits
llvm-commits at lists.llvm.org
Tue Aug 25 02:13:06 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-analysis
Author: Antonio Frighetto (antoniofrighetto)
<details>
<summary>Changes</summary>
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.
Compile-time impact: https://llvm-compile-time-tracker.com/compare.php?from=be7eff95e930f15745c64242ce35c07b1dc08803&to=8b8b052c52d32e4d48f8e7b937ed471843040acf&stat=instructions:u.
llvm-opt-benchmark: https://github.com/dtcxzyw/llvm-opt-benchmark-nightly/pull/1034 (observed to be a bit more pervasive on internal GPU kernel workloads).
Proofs: https://alive2.llvm.org/ce/z/D4cAVX.
Co-authored-by: Alex MacLean <amaclean@<!-- -->nvidia.com>
---
Full diff: https://github.com/llvm/llvm-project/pull/218639.diff
2 Files Affected:
- (modified) llvm/lib/Analysis/BasicAliasAnalysis.cpp (+54-4)
- (added) llvm/test/Analysis/BasicAA/gep-minabs-different-scale.ll (+111)
``````````diff
diff --git a/llvm/lib/Analysis/BasicAliasAnalysis.cpp b/llvm/lib/Analysis/BasicAliasAnalysis.cpp
index e33fde77cf365..061403d332ce0 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)
@@ -1298,12 +1310,15 @@ AliasResult BasicAAResult::aliasGEP(
APInt GCD;
ConstantRange OffsetRange = ConstantRange(DecompGEP1.Offset);
+ SmallVector<KnownBits, 4> VarIndexKnownBits;
+ VarIndexKnownBits.reserve(DecompGEP1.VarIndices.size());
for (unsigned i = 0, e = DecompGEP1.VarIndices.size(); i != e; ++i) {
const VariableGEPIndex &Index = DecompGEP1.VarIndices[i];
const APInt &Scale = Index.Scale;
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)
@@ -1386,7 +1401,8 @@ AliasResult BasicAAResult::aliasGEP(
};
// Try to determine the range of values for VarIndex such that
- // VarIndex <= -MinAbsVarIndex || MinAbsVarIndex <= VarIndex.
+ // VarIndex <= -MinAbsVarIndex || MinAbsVarIndex <= VarIndex, thus
+ // establishing a minimum absolute value of the variable offset.
std::optional<APInt> MinAbsVarIndex;
if (DecompGEP1.VarIndices.size() == 1) {
// VarIndex = Scale*V.
@@ -1407,14 +1423,48 @@ AliasResult BasicAAResult::aliasGEP(
// inequality of values across loop iterations.
const VariableGEPIndex &Var0 = DecompGEP1.VarIndices[0];
const VariableGEPIndex &Var1 = DecompGEP1.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)))
MinAbsVarIndex = Var0.Scale.abs();
+
+ // 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.
+ if (!MinAbsVarIndex && Preconditions) {
+ // 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(VarIndexKnownBits[0]),
+ KnownBits::makeConstant(C0));
+
+ auto Known1 =
+ KnownBits::mul(Var1.Val.evaluateWith(VarIndexKnownBits[1]),
+ KnownBits::makeConstant(C1));
+
+ if (auto Res = KnownBits::ne(Known0, Known1); Res && *Res)
+ MinAbsVarIndex = ScaleGCD;
+ }
+ }
}
if (MinAbsVarIndex) {
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..8e0e99150ba91
--- /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: NoAlias: 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: NoAlias: 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: NoAlias: 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: NoAlias: 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
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/218639
More information about the llvm-commits
mailing list