Temporarily revert "Reapply [LVI] Normalize pointer behavior" and "[LVI] Restructure caching"

This reverts commits 7e18aeba50 (D70376) 21fbd5587c (D69914) due to increased memory usage.
This commit is contained in:
Jordan Rupprecht 2019-12-20 10:25:57 -08:00
parent 3174683e21
commit 02a6b0bc3b
2 changed files with 176 additions and 137 deletions

View File

@ -136,9 +136,12 @@ namespace {
/// A callback value handle updates the cache when values are erased. /// A callback value handle updates the cache when values are erased.
class LazyValueInfoCache; class LazyValueInfoCache;
struct LVIValueHandle final : public CallbackVH { struct LVIValueHandle final : public CallbackVH {
// Needs to access getValPtr(), which is protected.
friend struct DenseMapInfo<LVIValueHandle>;
LazyValueInfoCache *Parent; LazyValueInfoCache *Parent;
LVIValueHandle(Value *V, LazyValueInfoCache *P = nullptr) LVIValueHandle(Value *V, LazyValueInfoCache *P)
: CallbackVH(V), Parent(P) { } : CallbackVH(V), Parent(P) { }
void deleted() override; void deleted() override;
@ -152,83 +155,89 @@ namespace {
/// This is the cache kept by LazyValueInfo which /// This is the cache kept by LazyValueInfo which
/// maintains information about queries across the clients' queries. /// maintains information about queries across the clients' queries.
class LazyValueInfoCache { class LazyValueInfoCache {
/// This is all of the cached information for one basic block. It contains /// This is all of the cached block information for exactly one Value*.
/// the per-value lattice elements, as well as a separate set for /// The entries are sorted by the BasicBlock* of the
/// overdefined values to reduce memory usage. Additionally pointers /// entries, allowing us to do a lookup with a binary search.
/// dereferenced in the block are cached for nullability queries. /// Over-defined lattice values are recorded in OverDefinedCache to reduce
struct BlockCacheEntryTy { /// memory overhead.
SmallDenseMap<AssertingVH<Value>, ValueLatticeElement, 4> LatticeElements; struct ValueCacheEntryTy {
SmallDenseSet<AssertingVH<Value>, 4> OverDefined; ValueCacheEntryTy(Value *V, LazyValueInfoCache *P) : Handle(V, P) {}
// None indicates that the dereferenced pointers for this basic block LVIValueHandle Handle;
// block have not been computed yet. SmallDenseMap<PoisoningVH<BasicBlock>, ValueLatticeElement, 4> BlockVals;
Optional<DenseSet<AssertingVH<Value>>> DereferencedPointers;
}; };
/// Cached information per basic block. /// This tracks, on a per-block basis, the set of values that are
DenseMap<PoisoningVH<BasicBlock>, BlockCacheEntryTy> BlockCache; /// over-defined at the end of that block.
/// Set of value handles used to erase values from the cache on deletion. typedef DenseMap<PoisoningVH<BasicBlock>, SmallPtrSet<Value *, 4>>
DenseSet<LVIValueHandle, DenseMapInfo<Value *>> ValueHandles; OverDefinedCacheTy;
/// Keep track of all blocks that we have ever seen, so we
/// don't spend time removing unused blocks from our caches.
DenseSet<PoisoningVH<BasicBlock> > SeenBlocks;
/// This is all of the cached information for all values,
/// mapped from Value* to key information.
DenseMap<Value *, std::unique_ptr<ValueCacheEntryTy>> ValueCache;
OverDefinedCacheTy OverDefinedCache;
public: public:
void insertResult(Value *Val, BasicBlock *BB, void insertResult(Value *Val, BasicBlock *BB,
const ValueLatticeElement &Result) { const ValueLatticeElement &Result) {
auto &CacheEntry = BlockCache.try_emplace(BB).first->second; SeenBlocks.insert(BB);
// Insert over-defined values into their own cache to reduce memory // Insert over-defined values into their own cache to reduce memory
// overhead. // overhead.
if (Result.isOverdefined()) if (Result.isOverdefined())
CacheEntry.OverDefined.insert(Val); OverDefinedCache[BB].insert(Val);
else else {
CacheEntry.LatticeElements.insert({ Val, Result }); auto It = ValueCache.find_as(Val);
if (It == ValueCache.end()) {
ValueCache[Val] = std::make_unique<ValueCacheEntryTy>(Val, this);
It = ValueCache.find_as(Val);
assert(It != ValueCache.end() && "Val was just added to the map!");
}
It->second->BlockVals[BB] = Result;
}
}
auto HandleIt = ValueHandles.find_as(Val); bool isOverdefined(Value *V, BasicBlock *BB) const {
if (HandleIt == ValueHandles.end()) auto ODI = OverDefinedCache.find(BB);
ValueHandles.insert({ Val, this });
if (ODI == OverDefinedCache.end())
return false;
return ODI->second.count(V);
} }
bool hasCachedValueInfo(Value *V, BasicBlock *BB) const { bool hasCachedValueInfo(Value *V, BasicBlock *BB) const {
auto It = BlockCache.find(BB); if (isOverdefined(V, BB))
if (It == BlockCache.end()) return true;
auto I = ValueCache.find_as(V);
if (I == ValueCache.end())
return false; return false;
return It->second.OverDefined.count(V) || return I->second->BlockVals.count(BB);
It->second.LatticeElements.count(V);
} }
ValueLatticeElement getCachedValueInfo(Value *V, BasicBlock *BB) const { ValueLatticeElement getCachedValueInfo(Value *V, BasicBlock *BB) const {
auto It = BlockCache.find(BB); if (isOverdefined(V, BB))
if (It == BlockCache.end())
return ValueLatticeElement();
if (It->second.OverDefined.count(V))
return ValueLatticeElement::getOverdefined(); return ValueLatticeElement::getOverdefined();
auto LatticeIt = It->second.LatticeElements.find(V); auto I = ValueCache.find_as(V);
if (LatticeIt == It->second.LatticeElements.end()) if (I == ValueCache.end())
return ValueLatticeElement(); return ValueLatticeElement();
auto BBI = I->second->BlockVals.find(BB);
return LatticeIt->second; if (BBI == I->second->BlockVals.end())
} return ValueLatticeElement();
return BBI->second;
bool isPointerDereferencedInBlock(
Value *V, BasicBlock *BB,
std::function<DenseSet<AssertingVH<Value>>(BasicBlock *)> InitFn) {
auto &CacheEntry = BlockCache.try_emplace(BB).first->second;
if (!CacheEntry.DereferencedPointers) {
CacheEntry.DereferencedPointers = InitFn(BB);
for (Value *V : *CacheEntry.DereferencedPointers) {
auto HandleIt = ValueHandles.find_as(V);
if (HandleIt == ValueHandles.end())
ValueHandles.insert({ V, this });
}
}
return CacheEntry.DereferencedPointers->count(V);
} }
/// clear - Empty the cache. /// clear - Empty the cache.
void clear() { void clear() {
BlockCache.clear(); SeenBlocks.clear();
ValueHandles.clear(); ValueCache.clear();
OverDefinedCache.clear();
} }
/// Inform the cache that a given value has been deleted. /// Inform the cache that a given value has been deleted.
@ -242,20 +251,23 @@ namespace {
/// OldSucc might have (unless also overdefined in NewSucc). This just /// OldSucc might have (unless also overdefined in NewSucc). This just
/// flushes elements from the cache and does not add any. /// flushes elements from the cache and does not add any.
void threadEdgeImpl(BasicBlock *OldSucc,BasicBlock *NewSucc); void threadEdgeImpl(BasicBlock *OldSucc,BasicBlock *NewSucc);
friend struct LVIValueHandle;
}; };
} }
void LazyValueInfoCache::eraseValue(Value *V) { void LazyValueInfoCache::eraseValue(Value *V) {
for (auto &Pair : BlockCache) { for (auto I = OverDefinedCache.begin(), E = OverDefinedCache.end(); I != E;) {
Pair.second.LatticeElements.erase(V); // Copy and increment the iterator immediately so we can erase behind
Pair.second.OverDefined.erase(V); // ourselves.
if (Pair.second.DereferencedPointers) auto Iter = I++;
Pair.second.DereferencedPointers->erase(V); SmallPtrSetImpl<Value *> &ValueSet = Iter->second;
ValueSet.erase(V);
if (ValueSet.empty())
OverDefinedCache.erase(Iter);
} }
auto HandleIt = ValueHandles.find_as(V); ValueCache.erase(V);
if (HandleIt != ValueHandles.end())
ValueHandles.erase(HandleIt);
} }
void LVIValueHandle::deleted() { void LVIValueHandle::deleted() {
@ -265,7 +277,18 @@ void LVIValueHandle::deleted() {
} }
void LazyValueInfoCache::eraseBlock(BasicBlock *BB) { void LazyValueInfoCache::eraseBlock(BasicBlock *BB) {
BlockCache.erase(BB); // Shortcut if we have never seen this block.
DenseSet<PoisoningVH<BasicBlock> >::iterator I = SeenBlocks.find(BB);
if (I == SeenBlocks.end())
return;
SeenBlocks.erase(I);
auto ODI = OverDefinedCache.find(BB);
if (ODI != OverDefinedCache.end())
OverDefinedCache.erase(ODI);
for (auto &I : ValueCache)
I.second->BlockVals.erase(BB);
} }
void LazyValueInfoCache::threadEdgeImpl(BasicBlock *OldSucc, void LazyValueInfoCache::threadEdgeImpl(BasicBlock *OldSucc,
@ -283,11 +306,10 @@ void LazyValueInfoCache::threadEdgeImpl(BasicBlock *OldSucc,
std::vector<BasicBlock*> worklist; std::vector<BasicBlock*> worklist;
worklist.push_back(OldSucc); worklist.push_back(OldSucc);
auto I = BlockCache.find(OldSucc); auto I = OverDefinedCache.find(OldSucc);
if (I == BlockCache.end() || I->second.OverDefined.empty()) if (I == OverDefinedCache.end())
return; // Nothing to process here. return; // Nothing to process here.
SmallVector<Value *, 4> ValsToClear(I->second.OverDefined.begin(), SmallVector<Value *, 4> ValsToClear(I->second.begin(), I->second.end());
I->second.OverDefined.end());
// Use a worklist to perform a depth-first search of OldSucc's successors. // Use a worklist to perform a depth-first search of OldSucc's successors.
// NOTE: We do not need a visited list since any blocks we have already // NOTE: We do not need a visited list since any blocks we have already
@ -301,10 +323,10 @@ void LazyValueInfoCache::threadEdgeImpl(BasicBlock *OldSucc,
if (ToUpdate == NewSucc) continue; if (ToUpdate == NewSucc) continue;
// If a value was marked overdefined in OldSucc, and is here too... // If a value was marked overdefined in OldSucc, and is here too...
auto OI = BlockCache.find(ToUpdate); auto OI = OverDefinedCache.find(ToUpdate);
if (OI == BlockCache.end() || OI->second.OverDefined.empty()) if (OI == OverDefinedCache.end())
continue; continue;
auto &ValueSet = OI->second.OverDefined; SmallPtrSetImpl<Value *> &ValueSet = OI->second;
bool changed = false; bool changed = false;
for (Value *V : ValsToClear) { for (Value *V : ValsToClear) {
@ -314,6 +336,11 @@ void LazyValueInfoCache::threadEdgeImpl(BasicBlock *OldSucc,
// If we removed anything, then we potentially need to update // If we removed anything, then we potentially need to update
// blocks successors too. // blocks successors too.
changed = true; changed = true;
if (ValueSet.empty()) {
OverDefinedCache.erase(OI);
break;
}
} }
if (!changed) continue; if (!changed) continue;
@ -415,7 +442,6 @@ namespace {
BasicBlock *BB); BasicBlock *BB);
bool solveBlockValueExtractValue(ValueLatticeElement &BBLV, bool solveBlockValueExtractValue(ValueLatticeElement &BBLV,
ExtractValueInst *EVI, BasicBlock *BB); ExtractValueInst *EVI, BasicBlock *BB);
bool isNonNullDueToDereferenceInBlock(Value *Val, BasicBlock *BB);
void intersectAssumeOrGuardBlockValueConstantRange(Value *Val, void intersectAssumeOrGuardBlockValueConstantRange(Value *Val,
ValueLatticeElement &BBLV, ValueLatticeElement &BBLV,
Instruction *BBI); Instruction *BBI);
@ -597,20 +623,6 @@ bool LazyValueInfoImpl::solveBlockValue(Value *Val, BasicBlock *BB) {
bool LazyValueInfoImpl::solveBlockValueImpl(ValueLatticeElement &Res, bool LazyValueInfoImpl::solveBlockValueImpl(ValueLatticeElement &Res,
Value *Val, BasicBlock *BB) { Value *Val, BasicBlock *BB) {
// If this value is a nonnull pointer, record it's range and bailout. Note
// that for all other pointer typed values, we terminate the search at the
// definition. We could easily extend this to look through geps, bitcasts,
// and the like to prove non-nullness, but it's not clear that's worth it
// compile time wise. The context-insensitive value walk done inside
// isKnownNonZero gets most of the profitable cases at much less expense.
// This does mean that we have a sensitivity to where the defining
// instruction is placed, even if it could legally be hoisted much higher.
// That is unfortunate.
PointerType *PT = dyn_cast<PointerType>(Val->getType());
if (PT && isKnownNonZero(Val, DL)) {
Res = ValueLatticeElement::getNot(ConstantPointerNull::get(PT));
return true;
}
Instruction *BBI = dyn_cast<Instruction>(Val); Instruction *BBI = dyn_cast<Instruction>(Val);
if (!BBI || BBI->getParent() != BB) if (!BBI || BBI->getParent() != BB)
@ -622,6 +634,20 @@ bool LazyValueInfoImpl::solveBlockValueImpl(ValueLatticeElement &Res,
if (auto *SI = dyn_cast<SelectInst>(BBI)) if (auto *SI = dyn_cast<SelectInst>(BBI))
return solveBlockValueSelect(Res, SI, BB); return solveBlockValueSelect(Res, SI, BB);
// If this value is a nonnull pointer, record it's range and bailout. Note
// that for all other pointer typed values, we terminate the search at the
// definition. We could easily extend this to look through geps, bitcasts,
// and the like to prove non-nullness, but it's not clear that's worth it
// compile time wise. The context-insensitive value walk done inside
// isKnownNonZero gets most of the profitable cases at much less expense.
// This does mean that we have a sensitivity to where the defining
// instruction is placed, even if it could legally be hoisted much higher.
// That is unfortunate.
PointerType *PT = dyn_cast<PointerType>(BBI->getType());
if (PT && isKnownNonZero(BBI, DL)) {
Res = ValueLatticeElement::getNot(ConstantPointerNull::get(PT));
return true;
}
if (BBI->getType()->isIntegerTy()) { if (BBI->getType()->isIntegerTy()) {
if (auto *CI = dyn_cast<CastInst>(BBI)) if (auto *CI = dyn_cast<CastInst>(BBI))
return solveBlockValueCast(Res, CI, BB); return solveBlockValueCast(Res, CI, BB);
@ -642,61 +668,75 @@ bool LazyValueInfoImpl::solveBlockValueImpl(ValueLatticeElement &Res,
return true; return true;
} }
static void AddDereferencedPointer( static bool InstructionDereferencesPointer(Instruction *I, Value *Ptr) {
Value *Ptr, DenseSet<AssertingVH<Value>> &PtrSet, const DataLayout &DL) {
// TODO: Use NullPointerIsDefined instead.
if (Ptr->getType()->getPointerAddressSpace() == 0) {
Ptr = GetUnderlyingObject(Ptr, DL);
PtrSet.insert(Ptr);
}
}
static void AddPointersDereferencedByInstruction(
Instruction *I, DenseSet<AssertingVH<Value>> &PtrSet,
const DataLayout &DL) {
if (LoadInst *L = dyn_cast<LoadInst>(I)) { if (LoadInst *L = dyn_cast<LoadInst>(I)) {
AddDereferencedPointer(L->getPointerOperand(), PtrSet, DL); return L->getPointerAddressSpace() == 0 &&
} else if (StoreInst *S = dyn_cast<StoreInst>(I)) { GetUnderlyingObject(L->getPointerOperand(),
AddDereferencedPointer(S->getPointerOperand(), PtrSet, DL); L->getModule()->getDataLayout()) == Ptr;
} else if (MemIntrinsic *MI = dyn_cast<MemIntrinsic>(I)) { }
if (MI->isVolatile()) return; if (StoreInst *S = dyn_cast<StoreInst>(I)) {
return S->getPointerAddressSpace() == 0 &&
GetUnderlyingObject(S->getPointerOperand(),
S->getModule()->getDataLayout()) == Ptr;
}
if (MemIntrinsic *MI = dyn_cast<MemIntrinsic>(I)) {
if (MI->isVolatile()) return false;
// FIXME: check whether it has a valuerange that excludes zero? // FIXME: check whether it has a valuerange that excludes zero?
ConstantInt *Len = dyn_cast<ConstantInt>(MI->getLength()); ConstantInt *Len = dyn_cast<ConstantInt>(MI->getLength());
if (!Len || Len->isZero()) return; if (!Len || Len->isZero()) return false;
AddDereferencedPointer(MI->getRawDest(), PtrSet, DL); if (MI->getDestAddressSpace() == 0)
if (GetUnderlyingObject(MI->getRawDest(),
MI->getModule()->getDataLayout()) == Ptr)
return true;
if (MemTransferInst *MTI = dyn_cast<MemTransferInst>(MI)) if (MemTransferInst *MTI = dyn_cast<MemTransferInst>(MI))
AddDereferencedPointer(MTI->getRawSource(), PtrSet, DL); if (MTI->getSourceAddressSpace() == 0)
if (GetUnderlyingObject(MTI->getRawSource(),
MTI->getModule()->getDataLayout()) == Ptr)
return true;
} }
return false;
} }
bool LazyValueInfoImpl::isNonNullDueToDereferenceInBlock( /// Return true if the allocation associated with Val is ever dereferenced
Value *Val, BasicBlock *BB) { /// within the given basic block. This establishes the fact Val is not null,
if (NullPointerIsDefined(BB->getParent(), /// but does not imply that the memory at Val is dereferenceable. (Val may
Val->getType()->getPointerAddressSpace())) /// point off the end of the dereferenceable part of the object.)
return false; static bool isObjectDereferencedInBlock(Value *Val, BasicBlock *BB) {
assert(Val->getType()->isPointerTy());
const DataLayout &DL = BB->getModule()->getDataLayout(); const DataLayout &DL = BB->getModule()->getDataLayout();
Val = GetUnderlyingObject(Val, DL); Value *UnderlyingVal = GetUnderlyingObject(Val, DL);
// If 'GetUnderlyingObject' didn't converge, skip it. It won't converge
return TheCache.isPointerDereferencedInBlock(Val, BB, [DL](BasicBlock *BB) { // inside InstructionDereferencesPointer either.
DenseSet<AssertingVH<Value>> DereferencedPointers; if (UnderlyingVal == GetUnderlyingObject(UnderlyingVal, DL, 1))
for (Instruction &I : *BB) for (Instruction &I : *BB)
AddPointersDereferencedByInstruction(&I, DereferencedPointers, DL); if (InstructionDereferencesPointer(&I, UnderlyingVal))
return DereferencedPointers; return true;
}); return false;
} }
bool LazyValueInfoImpl::solveBlockValueNonLocal(ValueLatticeElement &BBLV, bool LazyValueInfoImpl::solveBlockValueNonLocal(ValueLatticeElement &BBLV,
Value *Val, BasicBlock *BB) { Value *Val, BasicBlock *BB) {
ValueLatticeElement Result; // Start Undefined. ValueLatticeElement Result; // Start Undefined.
// If this is the entry block, we must be asking about an argument. The // If this is the entry block, we must be asking about an argument. The
// value is overdefined. // value is overdefined.
if (BB == &BB->getParent()->getEntryBlock()) { if (BB == &BB->getParent()->getEntryBlock()) {
assert(isa<Argument>(Val) && "Unknown live-in to the entry block"); assert(isa<Argument>(Val) && "Unknown live-in to the entry block");
BBLV = ValueLatticeElement::getOverdefined(); // Before giving up, see if we can prove the pointer non-null local to
// this particular block.
PointerType *PTy = dyn_cast<PointerType>(Val->getType());
if (PTy &&
(isKnownNonZero(Val, DL) ||
(isObjectDereferencedInBlock(Val, BB) &&
!NullPointerIsDefined(BB->getParent(), PTy->getAddressSpace())))) {
Result = ValueLatticeElement::getNot(ConstantPointerNull::get(PTy));
} else {
Result = ValueLatticeElement::getOverdefined();
}
BBLV = Result;
return true; return true;
} }
@ -722,6 +762,14 @@ bool LazyValueInfoImpl::solveBlockValueNonLocal(ValueLatticeElement &BBLV,
if (Result.isOverdefined()) { if (Result.isOverdefined()) {
LLVM_DEBUG(dbgs() << " compute BB '" << BB->getName() LLVM_DEBUG(dbgs() << " compute BB '" << BB->getName()
<< "' - overdefined because of pred (non local).\n"); << "' - overdefined because of pred (non local).\n");
// Before giving up, see if we can prove the pointer non-null local to
// this particular block.
PointerType *PTy = dyn_cast<PointerType>(Val->getType());
if (PTy && isObjectDereferencedInBlock(Val, BB) &&
!NullPointerIsDefined(BB->getParent(), PTy->getAddressSpace())) {
Result = ValueLatticeElement::getNot(ConstantPointerNull::get(PTy));
}
BBLV = Result; BBLV = Result;
return true; return true;
} }
@ -794,24 +842,16 @@ void LazyValueInfoImpl::intersectAssumeOrGuardBlockValueConstantRange(
// If guards are not used in the module, don't spend time looking for them // If guards are not used in the module, don't spend time looking for them
auto *GuardDecl = BBI->getModule()->getFunction( auto *GuardDecl = BBI->getModule()->getFunction(
Intrinsic::getName(Intrinsic::experimental_guard)); Intrinsic::getName(Intrinsic::experimental_guard));
if (GuardDecl && !GuardDecl->use_empty()) { if (!GuardDecl || GuardDecl->use_empty())
if (BBI->getIterator() == BBI->getParent()->begin()) return;
return;
for (Instruction &I : make_range(std::next(BBI->getIterator().getReverse()),
BBI->getParent()->rend())) {
Value *Cond = nullptr;
if (match(&I, m_Intrinsic<Intrinsic::experimental_guard>(m_Value(Cond))))
BBLV = intersect(BBLV, getValueFromCondition(Val, Cond));
}
}
if (BBLV.isOverdefined()) { if (BBI->getIterator() == BBI->getParent()->begin())
// Check whether we're checking at the terminator, and the pointer has return;
// been dereferenced in this block. for (Instruction &I : make_range(std::next(BBI->getIterator().getReverse()),
PointerType *PTy = dyn_cast<PointerType>(Val->getType()); BBI->getParent()->rend())) {
if (PTy && BBI->getParent()->getTerminator() == BBI && Value *Cond = nullptr;
isNonNullDueToDereferenceInBlock(Val, BBI->getParent())) if (match(&I, m_Intrinsic<Intrinsic::experimental_guard>(m_Value(Cond))))
BBLV = ValueLatticeElement::getNot(ConstantPointerNull::get(PTy)); BBLV = intersect(BBLV, getValueFromCondition(Val, Cond));
} }
} }

View File

@ -108,7 +108,7 @@ d2:
d3: d3:
%y = load i32*, i32** %ptr %y = load i32*, i32** %ptr
store i32 1, i32* %y store i32 1, i32* %y
%c2 = icmp eq i32* %y, @p %c2 = icmp eq i32* %y, null
br i1 %c2, label %ret1, label %ret2 br i1 %c2, label %ret1, label %ret2
ret1: ret1:
@ -118,6 +118,5 @@ ret2:
ret void ret void
} }
@p = external global i32
!0 = !{} !0 = !{}