Skip to content

Commit f29e3f7

Browse files
committed
[DSE] Delay deleting non-memory-defs until end of DSE.
DSE uses BatchAA, which caches queries using pairs of MemoryLocations. At the moment, DSE may remove instructions that are used as pointers in cached MemoryLocations. If a new instruction used by a new MemoryLoation and this instruction gets allocated at the same address as a previosuly cached and then removed instruction, we may access an incorrect entry in the cache. To avoid this delay removing all instructions except MemoryDefs until the end of DSE. This should avoid removing any values used in BatchAA's cache. Test case by @vporpo from llvm#83181. (Test not precommitted because the results are non-determinstic - memset only sometimes gets removed)
1 parent d1a461d commit f29e3f7

File tree

2 files changed

+68
-5
lines changed

2 files changed

+68
-5
lines changed

llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,9 @@ struct DSEState {
857857
// no longer be captured.
858858
bool ShouldIterateEndOfFunctionDSE;
859859

860+
/// Dead instructions to be removed at the end of DSE.
861+
SmallVector<Instruction *> ToRemove;
862+
860863
// Class contains self-reference, make sure it's not copied/moved.
861864
DSEState(const DSEState &) = delete;
862865
DSEState &operator=(const DSEState &) = delete;
@@ -1692,7 +1695,8 @@ struct DSEState {
16921695
return {MaybeDeadAccess};
16931696
}
16941697

1695-
// Delete dead memory defs
1698+
/// Delete dead memory defs and recursively add their operands to ToRemove if
1699+
/// they became dead.
16961700
void deleteDeadInstruction(Instruction *SI) {
16971701
MemorySSAUpdater Updater(&MSSA);
16981702
SmallVector<Instruction *, 32> NowDeadInsts;
@@ -1708,8 +1712,10 @@ struct DSEState {
17081712
salvageKnowledge(DeadInst);
17091713

17101714
// Remove the Instruction from MSSA.
1711-
if (MemoryAccess *MA = MSSA.getMemoryAccess(DeadInst)) {
1712-
if (MemoryDef *MD = dyn_cast<MemoryDef>(MA)) {
1715+
MemoryAccess *MA = MSSA.getMemoryAccess(DeadInst);
1716+
bool IsMemDef = MA && isa<MemoryDef>(MA);
1717+
if (MA) {
1718+
if (IsMemDef) {
17131719
SkipStores.insert(MD);
17141720
if (auto *SI = dyn_cast<StoreInst>(MD->getMemoryInst())) {
17151721
if (SI->getValueOperand()->getType()->isPointerTy()) {
@@ -1730,13 +1736,21 @@ struct DSEState {
17301736
// Remove its operands
17311737
for (Use &O : DeadInst->operands())
17321738
if (Instruction *OpI = dyn_cast<Instruction>(O)) {
1733-
O = nullptr;
1739+
O.set(PoisonValue::get(O->getType()));
17341740
if (isInstructionTriviallyDead(OpI, &TLI))
17351741
NowDeadInsts.push_back(OpI);
17361742
}
17371743

17381744
EI.removeInstruction(DeadInst);
1739-
DeadInst->eraseFromParent();
1745+
// Remove memory defs directly, but only queue other dead instructions for
1746+
// later removal. They may have been used as memory locations that have
1747+
// been cached by BatchAA. Removing them here may lead to newly created
1748+
// instructions to be allocated at the same address, yielding stale cache
1749+
// entries.
1750+
if (IsMemDef)
1751+
DeadInst->eraseFromParent();
1752+
else
1753+
ToRemove.push_back(DeadInst);
17401754
}
17411755
}
17421756

@@ -2287,6 +2301,13 @@ static bool eliminateDeadStores(Function &F, AliasAnalysis &AA, MemorySSA &MSSA,
22872301

22882302
MadeChange |= State.eliminateRedundantStoresOfExistingValues();
22892303
MadeChange |= State.eliminateDeadWritesAtEndOfFunction();
2304+
2305+
while (!State.ToRemove.empty()) {
2306+
Instruction *DeadInst = State.ToRemove.pop_back_val();
2307+
assert(!MSSA.getMemoryAccess(DeadInst));
2308+
DeadInst->eraseFromParent();
2309+
}
2310+
22902311
return MadeChange;
22912312
}
22922313
} // end anonymous namespace
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 4
2+
; RUN: opt -S -passes=dse < %s | FileCheck %s
3+
;
4+
; DSE kills `store i32 44, ptr %struct.byte.4, align 4` but should not kill
5+
; `call void @llvm.memset.p0.i64(...)` because it has a clobber read:
6+
; `%ret = load ptr, ptr %struct.byte.8`
7+
8+
9+
%struct.type = type { ptr, ptr }
10+
11+
define ptr @foo(ptr noundef %ptr) {
12+
; CHECK-LABEL: define ptr @foo(
13+
; CHECK-SAME: ptr noundef [[PTR:%.*]]) {
14+
; CHECK-NEXT: [[STRUCT_ALLOCA:%.*]] = alloca [[STRUCT_TYPE:%.*]], align 8
15+
; CHECK-NEXT: call void @llvm.lifetime.start.p0(i64 56, ptr nonnull [[STRUCT_ALLOCA]]) #[[ATTR2:[0-9]+]]
16+
; CHECK-NEXT: [[STRUCT_BYTE_8:%.*]] = getelementptr inbounds i8, ptr [[STRUCT_ALLOCA]], i64 8
17+
; CHECK-NEXT: [[TMP1:%.*]] = getelementptr inbounds i8, ptr [[STRUCT_BYTE_8]], i64 4
18+
; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr noundef nonnull align 4 [[TMP1]], i8 42, i64 4, i1 false)
19+
; CHECK-NEXT: store i32 43, ptr [[STRUCT_BYTE_8]], align 4
20+
; CHECK-NEXT: [[RET:%.*]] = load ptr, ptr [[STRUCT_BYTE_8]], align 8
21+
; CHECK-NEXT: call void @llvm.lifetime.end.p0(i64 56, ptr nonnull [[STRUCT_ALLOCA]]) #[[ATTR2]]
22+
; CHECK-NEXT: ret ptr [[RET]]
23+
;
24+
%struct.alloca = alloca %struct.type, align 8
25+
call void @llvm.lifetime.start.p0(i64 56, ptr nonnull %struct.alloca) nounwind
26+
%struct.byte.8 = getelementptr inbounds i8, ptr %struct.alloca, i64 8
27+
; Set %struct.alloca[8, 16) to 42.
28+
call void @llvm.memset.p0.i64(ptr noundef nonnull align 4 %struct.byte.8, i8 42, i64 8, i1 false)
29+
; Set %struct.alloca[8, 12) to 43.
30+
store i32 43, ptr %struct.byte.8, align 4
31+
; Set %struct.alloca[4, 8) to 44.
32+
%struct.byte.4 = getelementptr inbounds i8, ptr %struct.alloca, i64 4
33+
store i32 44, ptr %struct.byte.4, align 4
34+
; Return %struct.alloca[8, 16).
35+
%ret = load ptr, ptr %struct.byte.8
36+
call void @llvm.lifetime.end.p0(i64 56, ptr nonnull %struct.alloca) nounwind
37+
ret ptr %ret
38+
}
39+
40+
declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg)
41+
declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture)
42+
declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture)

0 commit comments

Comments
 (0)