[DSE] Don't remove stores made live by a call which unwinds.

Issue exposed by noalias or more aggressive alias analysis.

Fixes http://llvm.org/PR25422.

Differential revision: https://reviews.llvm.org/D21007



git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@278451 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Eli Friedman 2016-08-12 01:09:53 +00:00
parent 2d34392ce2
commit 35ff6f0857
4 changed files with 96 additions and 31 deletions

View File

@ -70,6 +70,7 @@ static void
deleteDeadInstruction(Instruction *I, BasicBlock::iterator *BBI, deleteDeadInstruction(Instruction *I, BasicBlock::iterator *BBI,
MemoryDependenceResults &MD, const TargetLibraryInfo &TLI, MemoryDependenceResults &MD, const TargetLibraryInfo &TLI,
InstOverlapIntervalsTy &IOL, InstOverlapIntervalsTy &IOL,
DenseMap<Instruction*, size_t> *InstrOrdering,
SmallSetVector<Value *, 16> *ValueSet = nullptr) { SmallSetVector<Value *, 16> *ValueSet = nullptr) {
SmallVector<Instruction*, 32> NowDeadInsts; SmallVector<Instruction*, 32> NowDeadInsts;
@ -102,15 +103,14 @@ deleteDeadInstruction(Instruction *I, BasicBlock::iterator *BBI,
NowDeadInsts.push_back(OpI); NowDeadInsts.push_back(OpI);
} }
if (ValueSet) ValueSet->remove(DeadInst);
InstrOrdering->erase(DeadInst);
IOL.erase(DeadInst);
if (NewIter == DeadInst->getIterator()) if (NewIter == DeadInst->getIterator())
NewIter = DeadInst->eraseFromParent(); NewIter = DeadInst->eraseFromParent();
else else
DeadInst->eraseFromParent(); DeadInst->eraseFromParent();
IOL.erase(DeadInst);
if (ValueSet) ValueSet->remove(DeadInst);
} while (!NowDeadInsts.empty()); } while (!NowDeadInsts.empty());
*BBI = NewIter; *BBI = NewIter;
} }
@ -590,7 +590,8 @@ static void findUnconditionalPreds(SmallVectorImpl<BasicBlock *> &Blocks,
static bool handleFree(CallInst *F, AliasAnalysis *AA, static bool handleFree(CallInst *F, AliasAnalysis *AA,
MemoryDependenceResults *MD, DominatorTree *DT, MemoryDependenceResults *MD, DominatorTree *DT,
const TargetLibraryInfo *TLI, const TargetLibraryInfo *TLI,
InstOverlapIntervalsTy &IOL) { InstOverlapIntervalsTy &IOL,
DenseMap<Instruction*, size_t> *InstrOrdering) {
bool MadeChange = false; bool MadeChange = false;
MemoryLocation Loc = MemoryLocation(F->getOperand(0)); MemoryLocation Loc = MemoryLocation(F->getOperand(0));
@ -622,7 +623,7 @@ static bool handleFree(CallInst *F, AliasAnalysis *AA,
// DCE instructions only used to calculate that store. // DCE instructions only used to calculate that store.
BasicBlock::iterator BBI(Dependency); BasicBlock::iterator BBI(Dependency);
deleteDeadInstruction(Dependency, &BBI, *MD, *TLI, IOL); deleteDeadInstruction(Dependency, &BBI, *MD, *TLI, IOL, InstrOrdering);
++NumFastStores; ++NumFastStores;
MadeChange = true; MadeChange = true;
@ -678,7 +679,8 @@ static void removeAccessedObjects(const MemoryLocation &LoadedLoc,
static bool handleEndBlock(BasicBlock &BB, AliasAnalysis *AA, static bool handleEndBlock(BasicBlock &BB, AliasAnalysis *AA,
MemoryDependenceResults *MD, MemoryDependenceResults *MD,
const TargetLibraryInfo *TLI, const TargetLibraryInfo *TLI,
InstOverlapIntervalsTy &IOL) { InstOverlapIntervalsTy &IOL,
DenseMap<Instruction*, size_t> *InstrOrdering) {
bool MadeChange = false; bool MadeChange = false;
// Keep track of all of the stack objects that are dead at the end of the // Keep track of all of the stack objects that are dead at the end of the
@ -737,7 +739,7 @@ static bool handleEndBlock(BasicBlock &BB, AliasAnalysis *AA,
dbgs() << '\n'); dbgs() << '\n');
// DCE instructions only used to calculate that store. // DCE instructions only used to calculate that store.
deleteDeadInstruction(Dead, &BBI, *MD, *TLI, IOL, &DeadStackObjects); deleteDeadInstruction(Dead, &BBI, *MD, *TLI, IOL, InstrOrdering, &DeadStackObjects);
++NumFastStores; ++NumFastStores;
MadeChange = true; MadeChange = true;
continue; continue;
@ -748,7 +750,7 @@ static bool handleEndBlock(BasicBlock &BB, AliasAnalysis *AA,
if (isInstructionTriviallyDead(&*BBI, TLI)) { if (isInstructionTriviallyDead(&*BBI, TLI)) {
DEBUG(dbgs() << "DSE: Removing trivially dead instruction:\n DEAD: " DEBUG(dbgs() << "DSE: Removing trivially dead instruction:\n DEAD: "
<< *&*BBI << '\n'); << *&*BBI << '\n');
deleteDeadInstruction(&*BBI, &BBI, *MD, *TLI, IOL, &DeadStackObjects); deleteDeadInstruction(&*BBI, &BBI, *MD, *TLI, IOL, InstrOrdering, &DeadStackObjects);
++NumFastOther; ++NumFastOther;
MadeChange = true; MadeChange = true;
continue; continue;
@ -947,7 +949,8 @@ static bool eliminateNoopStore(Instruction *Inst, BasicBlock::iterator &BBI,
AliasAnalysis *AA, MemoryDependenceResults *MD, AliasAnalysis *AA, MemoryDependenceResults *MD,
const DataLayout &DL, const DataLayout &DL,
const TargetLibraryInfo *TLI, const TargetLibraryInfo *TLI,
InstOverlapIntervalsTy &IOL) { InstOverlapIntervalsTy &IOL,
DenseMap<Instruction*, size_t> *InstrOrdering) {
// Must be a store instruction. // Must be a store instruction.
StoreInst *SI = dyn_cast<StoreInst>(Inst); StoreInst *SI = dyn_cast<StoreInst>(Inst);
if (!SI) if (!SI)
@ -962,7 +965,7 @@ static bool eliminateNoopStore(Instruction *Inst, BasicBlock::iterator &BBI,
DEBUG(dbgs() << "DSE: Remove Store Of Load from same pointer:\n LOAD: " DEBUG(dbgs() << "DSE: Remove Store Of Load from same pointer:\n LOAD: "
<< *DepLoad << "\n STORE: " << *SI << '\n'); << *DepLoad << "\n STORE: " << *SI << '\n');
deleteDeadInstruction(SI, &BBI, *MD, *TLI, IOL); deleteDeadInstruction(SI, &BBI, *MD, *TLI, IOL, InstrOrdering);
++NumRedundantStores; ++NumRedundantStores;
return true; return true;
} }
@ -980,7 +983,7 @@ static bool eliminateNoopStore(Instruction *Inst, BasicBlock::iterator &BBI,
dbgs() << "DSE: Remove null store to the calloc'ed object:\n DEAD: " dbgs() << "DSE: Remove null store to the calloc'ed object:\n DEAD: "
<< *Inst << "\n OBJECT: " << *UnderlyingPointer << '\n'); << *Inst << "\n OBJECT: " << *UnderlyingPointer << '\n');
deleteDeadInstruction(SI, &BBI, *MD, *TLI, IOL); deleteDeadInstruction(SI, &BBI, *MD, *TLI, IOL, InstrOrdering);
++NumRedundantStores; ++NumRedundantStores;
return true; return true;
} }
@ -994,6 +997,12 @@ static bool eliminateDeadStores(BasicBlock &BB, AliasAnalysis *AA,
const DataLayout &DL = BB.getModule()->getDataLayout(); const DataLayout &DL = BB.getModule()->getDataLayout();
bool MadeChange = false; bool MadeChange = false;
// FIXME: Maybe change this to use some abstraction like OrderedBasicBlock?
// The current OrderedBasicBlock can't deal with mutation at the moment.
size_t LastThrowingInstIndex = 0;
DenseMap<Instruction*, size_t> InstrOrdering;
size_t InstrIndex = 1;
// A map of interval maps representing partially-overwritten value parts. // A map of interval maps representing partially-overwritten value parts.
InstOverlapIntervalsTy IOL; InstOverlapIntervalsTy IOL;
@ -1001,7 +1010,7 @@ static bool eliminateDeadStores(BasicBlock &BB, AliasAnalysis *AA,
for (BasicBlock::iterator BBI = BB.begin(), BBE = BB.end(); BBI != BBE; ) { for (BasicBlock::iterator BBI = BB.begin(), BBE = BB.end(); BBI != BBE; ) {
// Handle 'free' calls specially. // Handle 'free' calls specially.
if (CallInst *F = isFreeCall(&*BBI, TLI)) { if (CallInst *F = isFreeCall(&*BBI, TLI)) {
MadeChange |= handleFree(F, AA, MD, DT, TLI, IOL); MadeChange |= handleFree(F, AA, MD, DT, TLI, IOL, &InstrOrdering);
// Increment BBI after handleFree has potentially deleted instructions. // Increment BBI after handleFree has potentially deleted instructions.
// This ensures we maintain a valid iterator. // This ensures we maintain a valid iterator.
++BBI; ++BBI;
@ -1010,12 +1019,19 @@ static bool eliminateDeadStores(BasicBlock &BB, AliasAnalysis *AA,
Instruction *Inst = &*BBI++; Instruction *Inst = &*BBI++;
size_t CurInstNumber = InstrIndex++;
InstrOrdering.insert(std::make_pair(Inst, CurInstNumber));
if (Inst->mayThrow()) {
LastThrowingInstIndex = CurInstNumber;
continue;
}
// Check to see if Inst writes to memory. If not, continue. // Check to see if Inst writes to memory. If not, continue.
if (!hasMemoryWrite(Inst, *TLI)) if (!hasMemoryWrite(Inst, *TLI))
continue; continue;
// eliminateNoopStore will update in iterator, if necessary. // eliminateNoopStore will update in iterator, if necessary.
if (eliminateNoopStore(Inst, BBI, AA, MD, DL, TLI, IOL)) { if (eliminateNoopStore(Inst, BBI, AA, MD, DL, TLI, IOL, &InstrOrdering)) {
MadeChange = true; MadeChange = true;
continue; continue;
} }
@ -1049,6 +1065,31 @@ static bool eliminateDeadStores(BasicBlock &BB, AliasAnalysis *AA,
if (!DepLoc.Ptr) if (!DepLoc.Ptr)
break; break;
// Make sure we don't look past a call which might throw. This is an
// issue because MemoryDependenceAnalysis works in the wrong direction:
// it finds instructions which dominate the current instruction, rather than
// instructions which are post-dominated by the current instruction.
//
// If the underlying object is a non-escaping memory allocation, any store
// to it is dead along the unwind edge. Otherwise, we need to preserve
// the store.
size_t DepIndex = InstrOrdering.lookup(DepWrite);
assert(DepIndex && "Unexpected instruction");
if (DepIndex <= LastThrowingInstIndex) {
const Value* Underlying = GetUnderlyingObject(DepLoc.Ptr, DL);
bool IsStoreDeadOnUnwind = isa<AllocaInst>(Underlying);
if (!IsStoreDeadOnUnwind) {
// We're looking for a call to an allocation function
// where the allocation doesn't escape before the last
// throwing instruction; PointerMayBeCaptured
// reasonably fast approximation.
IsStoreDeadOnUnwind = isAllocLikeFn(Underlying, TLI) &&
!PointerMayBeCaptured(Underlying, false, true);
}
if (!IsStoreDeadOnUnwind)
break;
}
// If we find a write that is a) removable (i.e., non-volatile), b) is // If we find a write that is a) removable (i.e., non-volatile), b) is
// completely obliterated by the store to 'Loc', and c) which we know that // completely obliterated by the store to 'Loc', and c) which we know that
// 'Inst' doesn't load from, then we can remove it. // 'Inst' doesn't load from, then we can remove it.
@ -1063,7 +1104,7 @@ static bool eliminateDeadStores(BasicBlock &BB, AliasAnalysis *AA,
<< *DepWrite << "\n KILLER: " << *Inst << '\n'); << *DepWrite << "\n KILLER: " << *Inst << '\n');
// Delete the store and now-dead instructions that feed it. // Delete the store and now-dead instructions that feed it.
deleteDeadInstruction(DepWrite, &BBI, *MD, *TLI, IOL); deleteDeadInstruction(DepWrite, &BBI, *MD, *TLI, IOL, &InstrOrdering);
++NumFastStores; ++NumFastStores;
MadeChange = true; MadeChange = true;
@ -1109,7 +1150,7 @@ static bool eliminateDeadStores(BasicBlock &BB, AliasAnalysis *AA,
// If this block ends in a return, unwind, or unreachable, all allocas are // If this block ends in a return, unwind, or unreachable, all allocas are
// dead at its end, which means stores to them are also dead. // dead at its end, which means stores to them are also dead.
if (BB.getTerminator()->getNumSuccessors() == 0) if (BB.getTerminator()->getNumSuccessors() == 0)
MadeChange |= handleEndBlock(BB, AA, MD, TLI, IOL); MadeChange |= handleEndBlock(BB, AA, MD, TLI, IOL, &InstrOrdering);
return MadeChange; return MadeChange;
} }
@ -1123,6 +1164,7 @@ static bool eliminateDeadStores(Function &F, AliasAnalysis *AA,
// cycles that will confuse alias analysis. // cycles that will confuse alias analysis.
if (DT->isReachableFromEntry(&BB)) if (DT->isReachableFromEntry(&BB))
MadeChange |= eliminateDeadStores(BB, AA, MD, DT, TLI); MadeChange |= eliminateDeadStores(BB, AA, MD, DT, TLI);
return MadeChange; return MadeChange;
} }

View File

@ -2,30 +2,30 @@
@X = internal global i32 4 @X = internal global i32 4
define void @test0() { define i32 @test0() {
; CHECK-LABEL: @test0 ; CHECK-LABEL: @test0
; CHECK: store i32 0, i32* @X ; CHECK: store i32 0, i32* @X
; CHECK-NEXT: call void @func_readonly() #0 ; CHECK-NEXT: call i32 @func_readonly() #0
; CHECK-NEXT: store i32 1, i32* @X ; CHECK-NEXT: store i32 1, i32* @X
store i32 0, i32* @X store i32 0, i32* @X
call void @func_readonly() #0 %x = call i32 @func_readonly() #0
store i32 1, i32* @X store i32 1, i32* @X
ret void ret i32 %x
} }
define void @test1() { define i32 @test1() {
; CHECK-LABEL: @test1 ; CHECK-LABEL: @test1
; CHECK-NOT: store ; CHECK-NOT: store
; CHECK: call void @func_read_argmem_only() #1 ; CHECK: call i32 @func_read_argmem_only() #1
; CHECK-NEXT: store i32 3, i32* @X ; CHECK-NEXT: store i32 3, i32* @X
store i32 2, i32* @X store i32 2, i32* @X
call void @func_read_argmem_only() #1 %x = call i32 @func_read_argmem_only() #1
store i32 3, i32* @X store i32 3, i32* @X
ret void ret i32 %x
} }
declare void @func_readonly() #0 declare i32 @func_readonly() #0
declare void @func_read_argmem_only() #1 declare i32 @func_read_argmem_only() #1
attributes #0 = { readonly } attributes #0 = { readonly nounwind }
attributes #1 = { readonly argmemonly } attributes #1 = { readonly argmemonly nounwind }

View File

@ -13,7 +13,7 @@ define void @test(i32* %Q, i32* %P) {
%DEAD = load i32, i32* %Q ; <i32> [#uses=1] %DEAD = load i32, i32* %Q ; <i32> [#uses=1]
store i32 %DEAD, i32* %P store i32 %DEAD, i32* %P
%1 = bitcast i32* %P to i8* %1 = bitcast i32* %P to i8*
tail call void @free(i8* %1) tail call void @free(i8* %1) nounwind
ret void ret void
} }
@ -25,7 +25,7 @@ define void @test2({i32, i32}* %P) {
%Q = getelementptr {i32, i32}, {i32, i32} *%P, i32 0, i32 1 %Q = getelementptr {i32, i32}, {i32, i32} *%P, i32 0, i32 1
store i32 4, i32* %Q store i32 4, i32* %Q
%1 = bitcast {i32, i32}* %P to i8* %1 = bitcast {i32, i32}* %P to i8*
tail call void @free(i8* %1) tail call void @free(i8* %1) nounwind
ret void ret void
} }
@ -37,7 +37,7 @@ define void @test3() {
store i8 0, i8* %m store i8 0, i8* %m
%m1 = getelementptr i8, i8* %m, i64 1 %m1 = getelementptr i8, i8* %m, i64 1
store i8 1, i8* %m1 store i8 1, i8* %m1
call void @free(i8* %m) call void @free(i8* %m) nounwind
ret void ret void
} }

View File

@ -498,3 +498,26 @@ bb3:
ret i32 0 ret i32 0
} }
; Don't remove redundant store: unknown_func could unwind
; CHECK-LABEL: @test34(
; CHECK: store i32 1
; CHECK: store i32 0
; CHECK: ret
define void @test34(i32* noalias %p) {
store i32 1, i32* %p
call void @unknown_func()
store i32 0, i32* %p
ret void
}
; Remove redundant store even with an unwinding function in the same block
; CHECK-LABEL: @test35(
; CHECK: call void @unknown_func
; CHECK-NEXT: store i32 0
; CHECK-NEXT: ret void
define void @test35(i32* noalias %p) {
call void @unknown_func()
store i32 1, i32* %p
store i32 0, i32* %p
ret void
}