Skip to content

[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

Merged
merged 2 commits into from
Apr 13, 2024
Merged

[DSE] Fix non-determinism due to address reuse #84943

merged 2 commits into from
Apr 13, 2024

Conversation

nikic
Copy link
Contributor

@nikic nikic commented Mar 12, 2024

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.

@nikic nikic requested review from fhahn and alinas March 12, 2024 16:32
@llvmbot
Copy link
Member

llvmbot commented Mar 12, 2024

@llvm/pr-subscribers-llvm-transforms

Author: Nikita Popov (nikic)

Changes

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.


Full diff: https://github.com/llvm/llvm-project/pull/84943.diff

1 Files Affected:

  • (modified) llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp (+10-5)
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;
         }

@nikic
Copy link
Contributor Author

nikic commented Mar 19, 2024

ping

1 similar comment
@nikic
Copy link
Contributor Author

nikic commented Apr 8, 2024

ping

Copy link
Contributor

@fhahn fhahn left a 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;
Copy link
Contributor

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);
Copy link
Contributor

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?

Copy link
Contributor Author

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?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

nikic added 2 commits April 11, 2024 18:08
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.
Copy link
Contributor

@fhahn fhahn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@nikic nikic merged commit 9e95c49 into llvm:main Apr 13, 2024
@nikic nikic deleted the dse-fix branch April 13, 2024 00:01
bazuzi pushed a commit to bazuzi/llvm-project that referenced this pull request Apr 15, 2024
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Optimization result is non-deterministic when compiling abc
3 participants