Skip to content

[BasicAA] Use nuw attribute of GEPs #98608

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 49 additions & 20 deletions llvm/lib/Analysis/BasicAliasAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -555,17 +555,17 @@ struct BasicAAResult::DecomposedGEP {
APInt Offset;
// Scaled variable (non-constant) indices.
SmallVector<VariableGEPIndex, 4> VarIndices;
// Are all operations inbounds GEPs or non-indexing operations?
// (std::nullopt iff expression doesn't involve any geps)
std::optional<bool> InBounds;
// Nowrap flags common to all GEP operations involved in expression.
GEPNoWrapFlags NWFlags = GEPNoWrapFlags::all();

void dump() const {
print(dbgs());
dbgs() << "\n";
}
void print(raw_ostream &OS) const {
OS << "(DecomposedGEP Base=" << Base->getName()
<< ", Offset=" << Offset
OS << ", inbounds=" << (NWFlags.isInBounds() ? "1" : "0")
<< ", nuw=" << (NWFlags.hasNoUnsignedWrap() ? "1" : "0")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd move these before Base as well, to match getelementptr syntax.

<< "(DecomposedGEP Base=" << Base->getName() << ", Offset=" << Offset
<< ", VarIndices=[";
for (size_t i = 0; i < VarIndices.size(); i++) {
if (i != 0)
Expand Down Expand Up @@ -644,12 +644,8 @@ BasicAAResult::DecomposeGEPExpression(const Value *V, const DataLayout &DL,
return Decomposed;
}

// Track whether we've seen at least one in bounds gep, and if so, whether
// all geps parsed were in bounds.
if (Decomposed.InBounds == std::nullopt)
Decomposed.InBounds = GEPOp->isInBounds();
else if (!GEPOp->isInBounds())
Decomposed.InBounds = false;
// Track the common nowrap flags for all GEPs we see.
Decomposed.NWFlags &= GEPOp->getNoWrapFlags();

assert(GEPOp->getSourceElementType()->isSized() && "GEP must be sized");

Expand Down Expand Up @@ -1112,6 +1108,13 @@ AliasResult BasicAAResult::aliasGEP(
if (DecompGEP1.Base == GEP1 && DecompGEP2.Base == V2)
return AliasResult::MayAlias;

// Swap GEP1 and GEP2 if GEP2 has more variable indices.
if (DecompGEP1.VarIndices.size() < DecompGEP2.VarIndices.size()) {
std::swap(DecompGEP1, DecompGEP2);
std::swap(V1Size, V2Size);
std::swap(UnderlyingV1, UnderlyingV2);
}

// Subtract the GEP2 pointer from the GEP1 pointer to find out their
// symbolic difference.
subtractDecomposedGEPs(DecompGEP1, DecompGEP2, AAQI);
Expand All @@ -1120,20 +1123,19 @@ AliasResult BasicAAResult::aliasGEP(
// for the two to alias, then we can assume noalias.
// TODO: Remove !isScalable() once BasicAA fully support scalable location
// size
if (*DecompGEP1.InBounds && DecompGEP1.VarIndices.empty() &&

if (DecompGEP1.NWFlags.isInBounds() && DecompGEP1.VarIndices.empty() &&
V2Size.hasValue() && !V2Size.isScalable() &&
DecompGEP1.Offset.sge(V2Size.getValue()) &&
isBaseOfObject(DecompGEP2.Base))
return AliasResult::NoAlias;

if (isa<GEPOperator>(V2)) {
// Symmetric case to above.
if (*DecompGEP2.InBounds && DecompGEP1.VarIndices.empty() &&
V1Size.hasValue() && !V1Size.isScalable() &&
DecompGEP1.Offset.sle(-V1Size.getValue()) &&
isBaseOfObject(DecompGEP1.Base))
return AliasResult::NoAlias;
}
// Symmetric case to above.
if (DecompGEP2.NWFlags.isInBounds() && DecompGEP1.VarIndices.empty() &&
V1Size.hasValue() && !V1Size.isScalable() &&
DecompGEP1.Offset.sle(-V1Size.getValue()) &&
isBaseOfObject(DecompGEP1.Base))
return AliasResult::NoAlias;

// For GEPs with identical offsets, we can preserve the size and AAInfo
// when performing the alias check on the underlying objects.
Expand Down Expand Up @@ -1239,6 +1241,20 @@ AliasResult BasicAAResult::aliasGEP(
}
}

// If the difference between pointers is Offset +<nuw> Indices then we know
// that the addition does not wrap the pointer index type (add nuw) and the
// constant Offset is a lower bound on the distance between the pointers. We
// can then prove NoAlias via Offset u>= VLeftSize.
// + + +
// | BaseOffset | +<nuw> Indices |
// ---------------->|-------------------->|
// |-->V2Size | |-------> V1Size
// LHS RHS
if (!DecompGEP1.VarIndices.empty() &&
DecompGEP1.NWFlags.hasNoUnsignedWrap() && V2Size.hasValue() &&
!V2Size.isScalable() && DecompGEP1.Offset.uge(V2Size.getValue()))
return AliasResult::NoAlias;

// Bail on analysing scalable LocationSize
if (V1Size.isScalable() || V2Size.isScalable())
return AliasResult::MayAlias;
Expand Down Expand Up @@ -1843,6 +1859,11 @@ bool BasicAAResult::isValueEqualInPotentialCycles(const Value *V,
void BasicAAResult::subtractDecomposedGEPs(DecomposedGEP &DestGEP,
const DecomposedGEP &SrcGEP,
const AAQueryInfo &AAQI) {
// Drop nuw flag from GEP if subtraction of constant offsets overflows in an
// unsigned sense.
if (DestGEP.Offset.ult(SrcGEP.Offset))
DestGEP.NWFlags = DestGEP.NWFlags.withoutNoUnsignedWrap();

DestGEP.Offset -= SrcGEP.Offset;
for (const VariableGEPIndex &Src : SrcGEP.VarIndices) {
// Find V in Dest. This is N^2, but pointer indices almost never have more
Expand All @@ -1865,6 +1886,11 @@ void BasicAAResult::subtractDecomposedGEPs(DecomposedGEP &DestGEP,
// If we found it, subtract off Scale V's from the entry in Dest. If it
// goes to zero, remove the entry.
if (Dest.Scale != Src.Scale) {
// Drop nuw flag from GEP if subtraction of V's Scale overflows in an
// unsigned sense.
if (Dest.Scale.ult(Src.Scale))
DestGEP.NWFlags = DestGEP.NWFlags.withoutNoUnsignedWrap();

Dest.Scale -= Src.Scale;
Dest.IsNSW = false;
} else {
Expand All @@ -1879,6 +1905,9 @@ void BasicAAResult::subtractDecomposedGEPs(DecomposedGEP &DestGEP,
VariableGEPIndex Entry = {Src.Val, Src.Scale, Src.CxtI, Src.IsNSW,
/* IsNegated */ true};
DestGEP.VarIndices.push_back(Entry);

// Drop nuw flag when we have unconsumed variable indices from SrcGEP.
DestGEP.NWFlags = DestGEP.NWFlags.withoutNoUnsignedWrap();
}
}
}
Expand Down
214 changes: 214 additions & 0 deletions llvm/test/Analysis/BasicAA/gep-nuw-alias.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
; RUN: opt < %s -aa-pipeline=basic-aa -passes=aa-eval -print-all-alias-modref-info -disable-output 2>&1 | FileCheck %s

; CHECK-LABEL: test_no_lower_bound
;
; CHECK-DAG: MayAlias: i32* %a, i32* %b
define void @test_no_lower_bound(ptr %p, i64 %i) {
%a = getelementptr i8, ptr %p, i64 4
%b = getelementptr nuw i8, ptr %p, i64 %i

load i32, ptr %a
load i32, ptr %b

ret void
}

; CHECK-LABEL: test_lower_bound_lt_size
;
; CHECK-DAG: MayAlias: i32* %a, i32* %b
define void @test_lower_bound_lt_size(ptr %p, i64 %i) {
%a = getelementptr i8, ptr %p
%add = getelementptr nuw i8, ptr %p, i64 2
%b = getelementptr nuw i8, ptr %add, i64 %i

load i32, ptr %a
load i32, ptr %b

ret void
}

; CHECK-LABEL: test_lower_bound_ge_size
;
; CHECK-DAG: NoAlias: i32* %a, i32* %b
define void @test_lower_bound_ge_size(ptr %p, i64 %i) {
%a = getelementptr i8, ptr %p
%add = getelementptr nuw i8, ptr %p, i64 4
%b = getelementptr nuw i8, ptr %add, i64 %i

load i32, ptr %a
load i32, ptr %b

ret void
}

; CHECK-LABEL: test_not_all_nuw
;
; If part of the addressing is done with non-nuw GEPs, we can't use properties
; implied by the last GEP with the whole offset. In this case, the calculation
; of %add (%p + 4) could wrap the pointer index type, such that %add +<nuw> %i
; could still alias with %p.
;
; CHECK-DAG: MayAlias: i32* %a, i32* %b
define void @test_not_all_nuw(ptr %p, i64 %i) {
%a = getelementptr i8, ptr %p
%add = getelementptr i8, ptr %p, i64 4
%b = getelementptr nuw i8, ptr %add, i64 %i

load i32, ptr %a
load i32, ptr %b

ret void
}

; CHECK-LABEL: test_multi_step_not_all_nuw
;
; CHECK-DAG: MayAlias: i32* %a, i32* %b
define void @test_multi_step_not_all_nuw(ptr %p, i64 %i, i64 %j, i64 %k) {
%a = getelementptr i8, ptr %p
%add = getelementptr i8, ptr %p, i64 4
%step1 = getelementptr i8, ptr %add, i64 %i
%step2 = getelementptr i8, ptr %step1, i64 %j
%b = getelementptr nuw i8, ptr %step2, i64 %k

load i32, ptr %a
load i32, ptr %b

ret void
}

; CHECK-LABEL: test_multi_step_all_nuw
;
; CHECK-DAG: NoAlias: i32* %a, i32* %b
define void @test_multi_step_all_nuw(ptr %p, i64 %i, i64 %j, i64 %k) {
%a = getelementptr i8, ptr %p
%add = getelementptr nuw i8, ptr %p, i64 4
%step1 = getelementptr nuw i8, ptr %add, i64 %i
%step2 = getelementptr nuw i8, ptr %step1, i64 %j
%b = getelementptr nuw i8, ptr %step2, i64 %k

load i32, ptr %a
load i32, ptr %b

ret void
}

%struct = type { i64, [2 x i32], i64 }

; CHECK-LABEL: test_struct_no_nuw
;
; The array access may alias with the struct elements before and after, because
; we cannot prove that (%arr + %i) does not alias with the base pointer %p.
;
; CHECK-DAG: MayAlias: i32* %arrayidx, i64* %st
; CHECK-DAG: NoAlias: i64* %after, i64* %st
; CHECK-DAG: MayAlias: i64* %after, i32* %arrayidx

define void @test_struct_no_nuw(ptr %st, i64 %i) {
%arr = getelementptr i8, ptr %st, i64 8
%arrayidx = getelementptr [2 x i32], ptr %arr, i64 0, i64 %i
%after = getelementptr i8, ptr %st, i64 16

load i64, ptr %st
load i32, ptr %arrayidx
load i64, ptr %after

ret void
}

; CHECK-LABEL: test_struct_nuw
;
; We can prove that the array access does not alias with struct element before,
; because we can prove that (%arr +<nuw> %i) does not wrap the pointer index
; type (add nuw). The array access may still alias with the struct element
; after, as the add nuw property does not preclude this.
;
; CHECK-DAG: NoAlias: i32* %arrayidx, i64* %st
; CHECK-DAG: NoAlias: i64* %after, i64* %st
; CHECK-DAG: MayAlias: i64* %after, i32* %arrayidx

define void @test_struct_nuw(ptr %st, i64 %i) {
%arr = getelementptr nuw i8, ptr %st, i64 8
%arrayidx = getelementptr nuw [2 x i32], ptr %arr, i64 0, i64 %i
%after = getelementptr nuw i8, ptr %st, i64 16

load i64, ptr %st
load i32, ptr %arrayidx
load i64, ptr %after

ret void
}

; CHECK-LABEL: constant_offset_overflow
;
; If subtraction of constant offsets could overflow in an unsigned sense, we
; cannot prove the lower bound between the GEPs and so they may still alias.
;
; CHECK-DAG: MayAlias: i32* %a, i32* %b

define void @constant_offset_overflow(ptr %p, i64 %i) {
%a = getelementptr i8, ptr %p, i64 -8
%add = getelementptr nuw i8, ptr %p, i64 4
%b = getelementptr nuw i8, ptr %add, i64 %i

load i32, ptr %a
load i32, ptr %b

ret void
}

; CHECK-LABEL: equal_var_idx_noalias
;
; If GEPs have equal variable indices, we can prove NoAlias when the Scale of
; the RHS GEP is greater, as in this scenario the constant lower bound holds.
;
; CHECK-DAG: NoAlias: i32* %a, i32* %b

define void @equal_var_idx_noalias(ptr %p, i64 %i) {
%a = getelementptr i8, ptr %p, i64 %i

%add = getelementptr nuw i8, ptr %p, i64 4
%b = getelementptr nuw i16, ptr %add, i64 %i

load i32, ptr %a
load i32, ptr %b

ret void
}

; CHECK-LABEL: equal_var_idx_alias
;
; If GEPs have equal variable indices, we cannot prove NoAlias when the Scale of
; the RHS GEP is ult Scale of the LHS GEP.
;
; CHECK-DAG: MayAlias: i32* %a, i32* %b

define void @equal_var_idx_alias(ptr %p, i64 %i) {
%a = getelementptr i32, ptr %p, i64 %i

%add = getelementptr nuw i8, ptr %p, i64 4
%b = getelementptr nuw i16, ptr %add, i64 %i

load i32, ptr %b
load i32, ptr %a

ret void
}

; CHECK-LABEL: both_var_idx
;
; If the RHS GEP has unmatched variable indices, we cannot prove a constant
; lower bound between GEPs.
;
; CHECK-DAG: MayAlias: i32* %a, i32* %b

define void @both_var_idx(ptr %p, i64 %i, i64 %j) {
%a = getelementptr i8, ptr %p, i64 %i

%add = getelementptr nuw i8, ptr %p, i64 4
%b = getelementptr nuw i8, ptr %add, i64 %j

load i32, ptr %a
load i32, ptr %b

ret void
}
Loading