AliasAnalysis: make AliasAnalysis a function analysis and simplify the cache keys

Instead of caching alias results globally for the module, make AliasAnalysis a FunctionAnalysisBase which caches the alias results per function.
Why?
* So far the result caches could only grow. They were reset when they reached a certain size. This was not ideal. Now, they are invalidated whenever the function changes.
* It was not possible to actually invalidate an alias analysis result. This is required, for example in TempRValueOpt and TempLValueOpt (so far it was done manually with invalidateInstruction).
* Type based alias analysis results were also cached for the whole module, while it is actually dependent on the function, because it depends on the function's resilience expansion. This was a potential bug.

I also added a new PassManager API to directly get a function-base analysis:
    getAnalysis(SILFunction *f)

The second change of this commit is the removal of the instruction-index indirection for the cache keys. Now the cache keys directly work on instruction pointers instead of instruction indices. This reduces the number of hash table lookups for a cache lookup from 3 to 1.
This indirection was needed to avoid dangling instruction pointers in the cache keys. But this is not needed anymore, because of the new delayed instruction deletion mechanism.
This commit is contained in:
Erik Eckstein
2021-05-26 21:36:56 +02:00
parent 24799e1526
commit d2fc6eb3b5
21 changed files with 194 additions and 339 deletions

View File

@@ -75,8 +75,6 @@ namespace {
///
/// TODO: Check if we still need to handle stores when RLE supports OSSA.
class TempRValueOptPass : public SILFunctionTransform {
AliasAnalysis *aa = nullptr;
bool collectLoads(Operand *addressUse, CopyAddrInst *originalCopy,
SmallPtrSetImpl<SILInstruction *> &loadInsts);
bool collectLoadsFromProjection(SingleValueInstruction *projection,
@@ -84,16 +82,17 @@ class TempRValueOptPass : public SILFunctionTransform {
SmallPtrSetImpl<SILInstruction *> &loadInsts);
SILInstruction *getLastUseWhileSourceIsNotModified(
CopyAddrInst *copyInst, const SmallPtrSetImpl<SILInstruction *> &useInsts);
CopyAddrInst *copyInst, const SmallPtrSetImpl<SILInstruction *> &useInsts,
AliasAnalysis *aa);
bool
checkTempObjectDestroy(AllocStackInst *tempObj, CopyAddrInst *copyInst);
bool extendAccessScopes(CopyAddrInst *copyInst, SILInstruction *lastUseInst);
bool extendAccessScopes(CopyAddrInst *copyInst, SILInstruction *lastUseInst,
AliasAnalysis *aa);
bool tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst);
std::pair<SILBasicBlock::iterator, bool>
tryOptimizeStoreIntoTemp(StoreInst *si);
void tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst);
SILBasicBlock::iterator tryOptimizeStoreIntoTemp(StoreInst *si);
void run() override;
};
@@ -306,7 +305,8 @@ collectLoads(Operand *addressUse, CopyAddrInst *originalCopy,
/// of the temporary and look for the last use, which effectively ends the
/// lifetime.
SILInstruction *TempRValueOptPass::getLastUseWhileSourceIsNotModified(
CopyAddrInst *copyInst, const SmallPtrSetImpl<SILInstruction *> &useInsts) {
CopyAddrInst *copyInst, const SmallPtrSetImpl<SILInstruction *> &useInsts,
AliasAnalysis *aa) {
if (useInsts.empty())
return copyInst;
unsigned numLoadsFound = 0;
@@ -358,7 +358,7 @@ SILInstruction *TempRValueOptPass::getLastUseWhileSourceIsNotModified(
/// We must not replace %temp with %a after the end_access. Instead we try to
/// move the end_access after "use %temp".
bool TempRValueOptPass::extendAccessScopes(
CopyAddrInst *copyInst, SILInstruction *lastUseInst) {
CopyAddrInst *copyInst, SILInstruction *lastUseInst, AliasAnalysis *aa) {
SILValue copySrc = copyInst->getSrc();
EndAccessInst *endAccessToMove = nullptr;
@@ -460,13 +460,13 @@ bool TempRValueOptPass::checkTempObjectDestroy(
}
/// Tries to perform the temporary rvalue copy elimination for \p copyInst
bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
void TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
if (!copyInst->isInitializationOfDest())
return false;
return;
auto *tempObj = dyn_cast<AllocStackInst>(copyInst->getDest());
if (!tempObj)
return false;
return;
bool isOSSA = copyInst->getFunction()->hasOwnership();
@@ -502,21 +502,23 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
// Otherwise we would risk of inserting the destroy too early.
// So we just treat the destroy_addr as any other use of tempObj.
if (user->getParent() != copyInst->getParent())
return false;
return;
loadInsts.insert(user);
}
continue;
}
if (!collectLoads(useOper, copyInst, loadInsts))
return false;
return;
}
AliasAnalysis *aa = getPassManager()->getAnalysis<AliasAnalysis>(getFunction());
// Check if the source is modified within the lifetime of the temporary.
SILInstruction *lastLoadInst = getLastUseWhileSourceIsNotModified(copyInst,
loadInsts);
SILInstruction *lastLoadInst =
getLastUseWhileSourceIsNotModified(copyInst, loadInsts, aa);
if (!lastLoadInst)
return false;
return;
// We cannot insert the destroy of copySrc after lastLoadInst if copySrc is
// re-initialized by exactly this instruction.
@@ -527,13 +529,13 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
if (needToInsertDestroy && lastLoadInst != copyInst &&
!isa<DestroyAddrInst>(lastLoadInst) &&
aa->mayWriteToMemory(lastLoadInst, copySrc))
return false;
return;
if (!isOSSA && !checkTempObjectDestroy(tempObj, copyInst))
return false;
return;
if (!extendAccessScopes(copyInst, lastLoadInst))
return false;
if (!extendAccessScopes(copyInst, lastLoadInst, aa))
return;
LLVM_DEBUG(llvm::dbgs() << " Success: replace temp" << *tempObj);
@@ -556,7 +558,6 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
while (!tempObj->use_empty()) {
Operand *use = *tempObj->use_begin();
SILInstruction *user = use->getUser();
aa->invalidateInstruction(user);
switch (user->getKind()) {
case SILInstructionKind::DestroyAddrInst:
@@ -591,25 +592,25 @@ bool TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
}
tempObj->eraseFromParent();
return true;
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
}
std::pair<SILBasicBlock::iterator, bool>
SILBasicBlock::iterator
TempRValueOptPass::tryOptimizeStoreIntoTemp(StoreInst *si) {
// If our store is an assign, bail.
if (si->getOwnershipQualifier() == StoreOwnershipQualifier::Assign)
return {std::next(si->getIterator()), false};
return std::next(si->getIterator());
auto *tempObj = dyn_cast<AllocStackInst>(si->getDest());
if (!tempObj) {
return {std::next(si->getIterator()), false};
return std::next(si->getIterator());
}
// If our tempObj has a dynamic lifetime (meaning it is conditionally
// initialized, conditionally taken, etc), we can not convert its uses to SSA
// while eliminating it simply. So bail.
if (tempObj->hasDynamicLifetime()) {
return {std::next(si->getIterator()), false};
return std::next(si->getIterator());
}
// Scan all uses of the temporary storage (tempObj) to verify they all refer
@@ -632,14 +633,14 @@ TempRValueOptPass::tryOptimizeStoreIntoTemp(StoreInst *si) {
break;
case SILInstructionKind::CopyAddrInst:
if (cast<CopyAddrInst>(user)->getDest() == tempObj)
return {std::next(si->getIterator()), false};
return std::next(si->getIterator());
break;
case SILInstructionKind::MarkDependenceInst:
if (cast<MarkDependenceInst>(user)->getValue() == tempObj)
return {std::next(si->getIterator()), false};
return std::next(si->getIterator());
break;
default:
return {std::next(si->getIterator()), false};
return std::next(si->getIterator());
}
}
@@ -734,7 +735,8 @@ TempRValueOptPass::tryOptimizeStoreIntoTemp(StoreInst *si) {
auto nextIter = std::next(si->getIterator());
si->eraseFromParent();
tempObj->eraseFromParent();
return {nextIter, true};
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
return nextIter;
}
//===----------------------------------------------------------------------===//
@@ -746,9 +748,6 @@ void TempRValueOptPass::run() {
LLVM_DEBUG(llvm::dbgs() << "Copy Peephole in Func "
<< getFunction()->getName() << "\n");
aa = getPassManager()->getAnalysis<AliasAnalysis>();
bool changed = false;
// Find all copy_addr instructions.
llvm::SmallSetVector<CopyAddrInst *, 8> deadCopies;
for (auto &block : *getFunction()) {
@@ -759,13 +758,12 @@ void TempRValueOptPass::run() {
if (auto *copyInst = dyn_cast<CopyAddrInst>(&*ii)) {
// In case of success, this may delete instructions, but not the
// CopyInst itself.
changed |= tryOptimizeCopyIntoTemp(copyInst);
tryOptimizeCopyIntoTemp(copyInst);
// Remove identity copies which either directly result from successfully
// calling tryOptimizeCopyIntoTemp or was created by an earlier
// iteration, where another copy_addr copied the temporary back to the
// source location.
if (copyInst->getSrc() == copyInst->getDest()) {
changed = true;
deadCopies.insert(copyInst);
}
++ii;
@@ -773,9 +771,7 @@ void TempRValueOptPass::run() {
}
if (auto *si = dyn_cast<StoreInst>(&*ii)) {
bool madeSingleChange;
std::tie(ii, madeSingleChange) = tryOptimizeStoreIntoTemp(si);
changed |= madeSingleChange;
ii = tryOptimizeStoreIntoTemp(si);
continue;
}
@@ -794,7 +790,6 @@ void TempRValueOptPass::run() {
DeadEndBlocks deBlocks(getFunction());
for (auto *deadCopy : deadCopies) {
assert(changed);
auto *srcInst = deadCopy->getSrc()->getDefiningInstruction();
deadCopy->eraseFromParent();
// Simplify any access scope markers that were only used by the dead
@@ -803,7 +798,7 @@ void TempRValueOptPass::run() {
simplifyAndReplaceAllSimplifiedUsesAndErase(srcInst, callbacks, &deBlocks);
}
}
if (changed) {
if (!deadCopies.empty()) {
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
}
}