Skip to content

Commit

Permalink
[Analysis] Make LocationSizes carry an 'imprecise' bit
Browse files Browse the repository at this point in the history
There are places where we need to merge multiple LocationSizes of
different sizes into one, and get a sensible result.

There are other places where we want to optimize aggressively based on
the value of a LocationSizes (e.g. how can a store of four bytes be to
an area of storage that's only two bytes large?)

This patch makes LocationSize hold an 'imprecise' bit to note whether
the LocationSize can be treated as an upper-bound and lower-bound for
the size of a location, or just an upper-bound.

This concludes the series of patches leading up to this. The most recent
of which is r344108.

Fixes PR36228.

Differential Revision: https://reviews.llvm.org/D44748

llvm-svn: 344114
  • Loading branch information
gburgessiv committed Oct 10, 2018
1 parent 1d893bf commit 40dc63e
Show file tree
Hide file tree
Showing 9 changed files with 285 additions and 47 deletions.
18 changes: 13 additions & 5 deletions llvm/include/llvm/Analysis/AliasSetTracker.h
Expand Up @@ -52,9 +52,13 @@ class AliasSet : public ilist_node<AliasSet> {
PointerRec **PrevInList = nullptr;
PointerRec *NextInList = nullptr;
AliasSet *AS = nullptr;
LocationSize Size = 0;
LocationSize Size = LocationSize::mapEmpty();
AAMDNodes AAInfo;

// Whether the size for this record has been set at all. This makes no
// guarantees about the size being known.
bool isSizeSet() const { return Size != LocationSize::mapEmpty(); }

public:
PointerRec(Value *V)
: Val(V), AAInfo(DenseMapInfo<AAMDNodes>::getEmptyKey()) {}
Expand All @@ -71,9 +75,10 @@ class AliasSet : public ilist_node<AliasSet> {

bool updateSizeAndAAInfo(LocationSize NewSize, const AAMDNodes &NewAAInfo) {
bool SizeChanged = false;
if (NewSize > Size) {
Size = NewSize;
SizeChanged = true;
if (NewSize != Size) {
LocationSize OldSize = Size;
Size = isSizeSet() ? Size.unionWith(NewSize) : NewSize;
SizeChanged = OldSize != Size;
}

if (AAInfo == DenseMapInfo<AAMDNodes>::getEmptyKey())
Expand All @@ -91,7 +96,10 @@ class AliasSet : public ilist_node<AliasSet> {
return SizeChanged;
}

LocationSize getSize() const { return Size; }
LocationSize getSize() const {
assert(isSizeSet() && "Getting an unset size!");
return Size;
}

/// Return the AAInfo, or null if there is no information or conflicting
/// information.
Expand Down
81 changes: 66 additions & 15 deletions llvm/include/llvm/Analysis/MemoryLocation.h
Expand Up @@ -34,18 +34,39 @@ class AnyMemIntrinsic;
class TargetLibraryInfo;

// Represents the size of a MemoryLocation. Logically, it's an
// Optional<uint64_t>.
// Optional<uint63_t> that also carries a bit to represent whether the integer
// it contains, N, is 'precise'. Precise, in this context, means that we know
// that the area of storage referenced by the given MemoryLocation must be
// precisely N bytes. An imprecise value is formed as the union of two or more
// precise values, and can conservatively represent all of the values unioned
// into it. Importantly, imprecise values are an *upper-bound* on the size of a
// MemoryLocation.
//
// We plan to use it for more soon, but we're trying to transition to this brave
// new world in small, easily-reviewable pieces; please see D44748.
// Concretely, a precise MemoryLocation is (%p, 4) in
// store i32 0, i32* %p
//
// Since we know that %p must be at least 4 bytes large at this point.
// Otherwise, we have UB. An example of an imprecise MemoryLocation is (%p, 4)
// at the memcpy in
//
// %n = select i1 %foo, i64 1, i64 4
// call void @llvm.memcpy.p0i8.p0i8.i64(i8* %p, i8* %baz, i64 %n, i32 1,
// i1 false)
//
// ...Since we'll copy *up to* 4 bytes into %p, but we can't guarantee that
// we'll ever actually do so.
//
// If asked to represent a pathologically large value, this will degrade to
// None.
class LocationSize {
enum : uint64_t {
Unknown = ~uint64_t(0),
ImpreciseBit = uint64_t(1) << 63,
MapEmpty = Unknown - 1,
MapTombstone = Unknown - 2,

// The maximum value we can represent without falling back to 'unknown'.
MaxValue = MapTombstone - 1,
MaxValue = (MapTombstone - 1) & ~ImpreciseBit,
};

uint64_t Value;
Expand All @@ -56,11 +77,28 @@ class LocationSize {

constexpr LocationSize(uint64_t Raw, DirectConstruction): Value(Raw) {}

static_assert(Unknown & ImpreciseBit, "Unknown is imprecise by definition.");
public:
// FIXME: Migrate all users to construct via either `precise` or `upperBound`,
// to make it more obvious at the callsite the kind of size that they're
// providing.
//
// Since the overwhelming majority of users of this provide precise values,
// this assumes the provided value is precise.
constexpr LocationSize(uint64_t Raw)
: Value(Raw > MaxValue ? Unknown : Raw) {}

static LocationSize precise(uint64_t Value) { return LocationSize(Value); }

static LocationSize upperBound(uint64_t Value) {
// You can't go lower than 0, so give a precise result.
if (LLVM_UNLIKELY(Value == 0))
return precise(0);
if (LLVM_UNLIKELY(Value > MaxValue))
return unknown();
return LocationSize(Value | ImpreciseBit, Direct);
}

constexpr static LocationSize unknown() {
return LocationSize(Unknown, Direct);
}
Expand All @@ -73,10 +111,28 @@ class LocationSize {
return LocationSize(MapEmpty, Direct);
}

// Returns a LocationSize that can correctly represent either `*this` or
// `Other`.
LocationSize unionWith(LocationSize Other) const {
if (Other == *this)
return *this;

if (!hasValue() || !Other.hasValue())
return unknown();

return upperBound(std::max(getValue(), Other.getValue()));
}

bool hasValue() const { return Value != Unknown; }
uint64_t getValue() const {
assert(hasValue() && "Getting value from an unknown LocationSize!");
return Value;
return Value & ~ImpreciseBit;
}

// Returns whether or not this value is precise. Note that if a value is
// precise, it's guaranteed to not be `unknown()`.
bool isPrecise() const {
return (Value & ImpreciseBit) == 0;
}

bool operator==(const LocationSize &Other) const {
Expand All @@ -87,21 +143,16 @@ class LocationSize {
return !(*this == Other);
}

// Ordering operators are not provided, since it's unclear if there's only one
// reasonable way to compare:
// - values that don't exist against values that do, and
// - precise values to imprecise values

void print(raw_ostream &OS) const;

// Returns an opaque value that represents this LocationSize. Cannot be
// reliably converted back into a LocationSize.
uint64_t toRaw() const { return Value; }

// NOTE: These comparison operators will go away with D44748. Please don't
// rely on them.
bool operator<(const LocationSize &Other) const {
return Value < Other.Value;
}

bool operator>(const LocationSize &Other) const {
return Other < *this;
}
};

inline raw_ostream &operator<<(raw_ostream &OS, LocationSize Size) {
Expand Down
13 changes: 5 additions & 8 deletions llvm/lib/Analysis/BasicAliasAnalysis.cpp
Expand Up @@ -1715,12 +1715,10 @@ AliasResult BasicAAResult::aliasCheck(const Value *V1, LocationSize V1Size,
// If the size of one access is larger than the entire object on the other
// side, then we know such behavior is undefined and can assume no alias.
bool NullIsValidLocation = NullPointerIsDefined(&F);
if ((V1Size != MemoryLocation::UnknownSize &&
isObjectSmallerThan(O2, V1Size.getValue(), DL, TLI,
NullIsValidLocation)) ||
(V2Size != MemoryLocation::UnknownSize &&
isObjectSmallerThan(O1, V2Size.getValue(), DL, TLI,
NullIsValidLocation)))
if ((V1Size.isPrecise() && isObjectSmallerThan(O2, V1Size.getValue(), DL, TLI,
NullIsValidLocation)) ||
(V2Size.isPrecise() && isObjectSmallerThan(O1, V2Size.getValue(), DL, TLI,
NullIsValidLocation)))
return NoAlias;

// Check the cache before climbing up use-def chains. This also terminates
Expand Down Expand Up @@ -1778,8 +1776,7 @@ AliasResult BasicAAResult::aliasCheck(const Value *V1, LocationSize V1Size,
// If both pointers are pointing into the same object and one of them
// accesses the entire object, then the accesses must overlap in some way.
if (O1 == O2)
if (V1Size != MemoryLocation::UnknownSize &&
V2Size != MemoryLocation::UnknownSize &&
if (V1Size.isPrecise() && V2Size.isPrecise() &&
(isObjectSize(O1, V1Size.getValue(), DL, TLI, NullIsValidLocation) ||
isObjectSize(O2, V2Size.getValue(), DL, TLI, NullIsValidLocation)))
return AliasCache[Locs] = PartialAlias;
Expand Down
45 changes: 30 additions & 15 deletions llvm/lib/Analysis/MemoryDependenceAnalysis.cpp
Expand Up @@ -1113,21 +1113,36 @@ bool MemoryDependenceResults::getNonLocalPointerDepFromBB(
// If we already have a cache entry for this CacheKey, we may need to do some
// work to reconcile the cache entry and the current query.
if (!Pair.second) {
if (CacheInfo->Size < Loc.Size) {
// The query's Size is greater than the cached one. Throw out the
// cached data and proceed with the query at the greater size.
CacheInfo->Pair = BBSkipFirstBlockPair();
CacheInfo->Size = Loc.Size;
for (auto &Entry : CacheInfo->NonLocalDeps)
if (Instruction *Inst = Entry.getResult().getInst())
RemoveFromReverseMap(ReverseNonLocalPtrDeps, Inst, CacheKey);
CacheInfo->NonLocalDeps.clear();
} else if (CacheInfo->Size > Loc.Size) {
// This query's Size is less than the cached one. Conservatively restart
// the query using the greater size.
return getNonLocalPointerDepFromBB(
QueryInst, Pointer, Loc.getWithNewSize(CacheInfo->Size), isLoad,
StartBB, Result, Visited, SkipFirstBlock);
if (CacheInfo->Size != Loc.Size) {
bool ThrowOutEverything;
if (CacheInfo->Size.hasValue() && Loc.Size.hasValue()) {
// FIXME: We may be able to do better in the face of results with mixed
// precision. We don't appear to get them in practice, though, so just
// be conservative.
ThrowOutEverything =
CacheInfo->Size.isPrecise() != Loc.Size.isPrecise() ||
CacheInfo->Size.getValue() < Loc.Size.getValue();
} else {
// For our purposes, unknown size > all others.
ThrowOutEverything = !Loc.Size.hasValue();
}

if (ThrowOutEverything) {
// The query's Size is greater than the cached one. Throw out the
// cached data and proceed with the query at the greater size.
CacheInfo->Pair = BBSkipFirstBlockPair();
CacheInfo->Size = Loc.Size;
for (auto &Entry : CacheInfo->NonLocalDeps)
if (Instruction *Inst = Entry.getResult().getInst())
RemoveFromReverseMap(ReverseNonLocalPtrDeps, Inst, CacheKey);
CacheInfo->NonLocalDeps.clear();
} else {
// This query's Size is less than the cached one. Conservatively restart
// the query using the greater size.
return getNonLocalPointerDepFromBB(
QueryInst, Pointer, Loc.getWithNewSize(CacheInfo->Size), isLoad,
StartBB, Result, Visited, SkipFirstBlock);
}
}

// If the query's AATags are inconsistent with the cached one,
Expand Down
4 changes: 3 additions & 1 deletion llvm/lib/Analysis/MemoryLocation.cpp
Expand Up @@ -26,8 +26,10 @@ void LocationSize::print(raw_ostream &OS) const {
OS << "mapEmpty";
else if (*this == mapTombstone())
OS << "mapTombstone";
else
else if (isPrecise())
OS << "precise(" << getValue() << ')';
else
OS << "upperBound(" << getValue() << ')';
}

MemoryLocation MemoryLocation::get(const LoadInst *LI) {
Expand Down
6 changes: 3 additions & 3 deletions llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
Expand Up @@ -349,9 +349,9 @@ static OverwriteResult isOverwrite(const MemoryLocation &Later,
InstOverlapIntervalsTy &IOL,
AliasAnalysis &AA,
const Function *F) {
// If we don't know the sizes of either access, then we can't do a comparison.
if (Later.Size == MemoryLocation::UnknownSize ||
Earlier.Size == MemoryLocation::UnknownSize)
// FIXME: Vet that this works for size upper-bounds. Seems unlikely that we'll
// get imprecise values here, though (except for unknown sizes).
if (!Later.Size.isPrecise() || !Earlier.Size.isPrecise())
return OW_Unknown;

const uint64_t LaterSize = Later.Size.getValue();
Expand Down
40 changes: 40 additions & 0 deletions llvm/test/Transforms/LICM/pr36228.ll
@@ -0,0 +1,40 @@
; RUN: opt -S -licm -o - %s | FileCheck %s
;
; Be sure that we don't hoist loads incorrectly if a loop has conditional UB.
; See PR36228.

declare void @check(i8)
declare void @llvm.memcpy.p0i8.p0i8.i64(i8*, i8*, i64, i32, i1)

; CHECK-LABEL: define void @buggy
define void @buggy(i8* %src, i1* %kOne) {
entry:
%dst = alloca [1 x i8], align 1
%0 = getelementptr inbounds [1 x i8], [1 x i8]* %dst, i64 0, i64 0
store i8 42, i8* %0, align 1
%src16 = bitcast i8* %src to i16*
%srcval = load i16, i16* %src16
br label %while.cond

while.cond: ; preds = %if.end, %entry
%dp.0 = phi i8* [ %0, %entry ], [ %dp.1, %if.end ]
%1 = load volatile i1, i1* %kOne, align 4
br i1 %1, label %if.else, label %if.then

if.then: ; preds = %while.cond
store i8 9, i8* %dp.0, align 1
br label %if.end

if.else: ; preds = %while.cond
call void @llvm.memcpy.p0i8.p0i8.i64(i8* %dp.0, i8* %src, i64 2, i32 1, i1 false)
%dp.new = getelementptr inbounds i8, i8* %dp.0, i64 1
br label %if.end

if.end: ; preds = %if.else, %if.then
%dp.1 = phi i8* [ %dp.0, %if.then ], [ %dp.new, %if.else ]
; CHECK: %2 = load i8, i8* %0
%2 = load i8, i8* %0, align 1
; CHECK-NEXT: call void @check(i8 %2)
call void @check(i8 %2)
br label %while.cond
}

0 comments on commit 40dc63e

Please sign in to comment.