mirror of
https://github.com/RPCSX/llvm.git
synced 2024-11-24 20:29:53 +00:00
[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:
parent
2d34392ce2
commit
35ff6f0857
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 }
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user