-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[DSE] Fix non-determinism due to address reuse #84943
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@llvm/pr-subscribers-llvm-transforms Author: Nikita Popov (nikic) ChangesThe malloc->calloc fold creates a new MemoryAccess, which may end of at the same address as a previously deleted access inside SkipStores. To the most part, this is not a problem, because SkipStores is normally only used together with MemDefs. Neither the old malloc access nor the new calloc access will be part of MemDefs, so there is no problem here. However, SkipStores is also used in one more place: In the main DSE loop, ToCheck entries are checked against it. Fix this by not using SkipStores here, and instead using a separate set to track deletions inside this loop. This way it is not affected by the calloc optimization that happens outside it. This is all pretty ugly, but I haven't found another good way to fix it. Suggestions welcome. No test case as I don't have a reliable DSE-only test-case for this. Fixes #84458. Full diff: https://github.com/llvm/llvm-project/pull/84943.diff 1 Files Affected:
diff --git a/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp b/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
index 392e6ad5a66bb9..865999cb7ca30b 100644
--- a/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
@@ -1698,7 +1698,9 @@ struct DSEState {
/// Delete dead memory defs and recursively add their operands to ToRemove if
/// they became dead.
- void deleteDeadInstruction(Instruction *SI) {
+ void
+ deleteDeadInstruction(Instruction *SI,
+ SmallPtrSetImpl<MemoryAccess *> *Deleted = nullptr) {
MemorySSAUpdater Updater(&MSSA);
SmallVector<Instruction *, 32> NowDeadInsts;
NowDeadInsts.push_back(SI);
@@ -1719,6 +1721,8 @@ struct DSEState {
if (IsMemDef) {
auto *MD = cast<MemoryDef>(MA);
SkipStores.insert(MD);
+ if (Deleted)
+ Deleted->insert(MD);
if (auto *SI = dyn_cast<StoreInst>(MD->getMemoryInst())) {
if (SI->getValueOperand()->getType()->isPointerTy()) {
const Value *UO = getUnderlyingObject(SI->getValueOperand());
@@ -2167,6 +2171,7 @@ static bool eliminateDeadStores(Function &F, AliasAnalysis &AA, MemorySSA &MSSA,
unsigned PartialLimit = MemorySSAPartialStoreLimit;
// Worklist of MemoryAccesses that may be killed by KillingDef.
SmallSetVector<MemoryAccess *, 8> ToCheck;
+ SmallPtrSet<MemoryAccess *, 8> Deleted;
ToCheck.insert(KillingDef->getDefiningAccess());
bool Shortend = false;
@@ -2174,7 +2179,7 @@ static bool eliminateDeadStores(Function &F, AliasAnalysis &AA, MemorySSA &MSSA,
// Check if MemoryAccesses in the worklist are killed by KillingDef.
for (unsigned I = 0; I < ToCheck.size(); I++) {
MemoryAccess *Current = ToCheck[I];
- if (State.SkipStores.count(Current))
+ if (Deleted.contains(Current))
continue;
std::optional<MemoryAccess *> MaybeDeadAccess = State.getDomMemoryDef(
@@ -2221,7 +2226,7 @@ static bool eliminateDeadStores(Function &F, AliasAnalysis &AA, MemorySSA &MSSA,
continue;
LLVM_DEBUG(dbgs() << "DSE: Remove Dead Store:\n DEAD: " << *DeadI
<< "\n KILLER: " << *KillingI << '\n');
- State.deleteDeadInstruction(DeadI);
+ State.deleteDeadInstruction(DeadI, &Deleted);
++NumFastStores;
MadeChange = true;
} else {
@@ -2258,7 +2263,7 @@ static bool eliminateDeadStores(Function &F, AliasAnalysis &AA, MemorySSA &MSSA,
Shortend = true;
// Remove killing store and remove any outstanding overlap
// intervals for the updated store.
- State.deleteDeadInstruction(KillingSI);
+ State.deleteDeadInstruction(KillingSI, &Deleted);
auto I = State.IOLs.find(DeadSI->getParent());
if (I != State.IOLs.end())
I->second.erase(DeadSI);
@@ -2270,7 +2275,7 @@ static bool eliminateDeadStores(Function &F, AliasAnalysis &AA, MemorySSA &MSSA,
if (OR == OW_Complete) {
LLVM_DEBUG(dbgs() << "DSE: Remove Dead Store:\n DEAD: " << *DeadI
<< "\n KILLER: " << *KillingI << '\n');
- State.deleteDeadInstruction(DeadI);
+ State.deleteDeadInstruction(DeadI, &Deleted);
++NumFastStores;
MadeChange = true;
}
|
ping |
1 similar comment
ping |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the patch, I can't really think of a better solution at the moment.
@@ -2167,14 +2171,15 @@ static bool eliminateDeadStores(Function &F, AliasAnalysis &AA, MemorySSA &MSSA, | |||
unsigned PartialLimit = MemorySSAPartialStoreLimit; | |||
// Worklist of MemoryAccesses that may be killed by KillingDef. | |||
SmallSetVector<MemoryAccess *, 8> ToCheck; | |||
SmallPtrSet<MemoryAccess *, 8> Deleted; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a comment here explaining why this extra set is needed here?
@@ -2221,7 +2226,7 @@ static bool eliminateDeadStores(Function &F, AliasAnalysis &AA, MemorySSA &MSSA, | |||
continue; | |||
LLVM_DEBUG(dbgs() << "DSE: Remove Dead Store:\n DEAD: " << *DeadI | |||
<< "\n KILLER: " << *KillingI << '\n'); | |||
State.deleteDeadInstruction(DeadI); | |||
State.deleteDeadInstruction(DeadI, &Deleted); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to check if all uses in the loop pass Deleted, e.g. add an assert that checks if we add as many elements to SkipStore
as we do to Deleted
after the loop?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added an assertion -- does that match what you had in mind?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
The malloc->calloc fold creates a new MemoryAccess, which may end of at the same address as a previously deleted access inside SkipStores. To the most part, this is not a problem, because SkipStores is normally only used together with MemDefs. Neither the old malloc access nor the new calloc access will be part of MemDefs, so there is no problem here. However, SkipStores is also used in one more place: In the main DSE loop, ToCheck entries are checked against it. Fix this by not using SkipStores here, and instead using a separate set to track deletions inside this loop. This way it is not affected by the calloc optimization that happens outside it. This is all pretty ugly, but I haven't found another good way to fix it. Suggestions welcome. No test case as I don't have a reliable DSE-only test-case for this. Fixes llvm#84458.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks!
The malloc->calloc fold creates a new MemoryAccess, which may end of at the same address as a previously deleted access inside SkipStores. To the most part, this is not a problem, because SkipStores is normally only used together with MemDefs. Neither the old malloc access nor the new calloc access will be part of MemDefs, so there is no problem here. However, SkipStores is also used in one more place: In the main DSE loop, ToCheck entries are checked against it. Fix this by not using SkipStores here, and instead using a separate set to track deletions inside this loop. This way it is not affected by the calloc optimization that happens outside it. This is all pretty ugly, but I haven't found another good way to fix it. Suggestions welcome. No test case as I don't have a reliable DSE-only test-case for this. Fixes llvm#84458.
The malloc->calloc fold creates a new MemoryAccess, which may end of at the same address as a previously deleted access inside SkipStores.
To the most part, this is not a problem, because SkipStores is normally only used together with MemDefs. Neither the old malloc access nor the new calloc access will be part of MemDefs, so there is no problem here.
However, SkipStores is also used in one more place: In the main DSE loop, ToCheck entries are checked against it. Fix this by not using SkipStores here, and instead using a separate set to track deletions inside this loop. This way it is not affected by the calloc optimization that happens outside it.
This is all pretty ugly, but I haven't found another good way to fix it. Suggestions welcome.
No test case as I don't have a reliable DSE-only test-case for this.
Fixes #84458.