Skip to content

[mlir] Add requiresReplacedValues and visitReplacedValues to PromotableOpInterface #86792

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 5 commits into from
Apr 4, 2024

Conversation

fabianmcg
Copy link
Contributor

@fabianmcg fabianmcg commented Mar 27, 2024

Add requiresReplacedValues and visitReplacedValues methods to PromotableOpInterface. These methods allow PromotableOpInterface ops to transforms definitions mutated by a store.

This change is necessary to correctly handle the promotion of LLVM_DbgDeclareOp. It is also necessary for enabling #73057

…romotableOpInterface`

Add `requiresVisitingMutatedDefs` and `visitMutatedDefs` methods to
`PromotableOpInterface`. These methods allow `PromotableOpInterface` ops to
transforms definitions mutated by a `store`.

This change is necessary to correctly handle the promotion of
`LLVM_DbgDeclareOp`.
@fabianmcg fabianmcg marked this pull request as ready for review March 27, 2024 12:08
@fabianmcg fabianmcg requested a review from gysit March 27, 2024 12:09
@llvmbot llvmbot added mlir:core MLIR Core Infrastructure mlir:llvm mlir labels Mar 27, 2024
@llvmbot
Copy link
Member

llvmbot commented Mar 27, 2024

@llvm/pr-subscribers-mlir-llvm
@llvm/pr-subscribers-mlir-core

@llvm/pr-subscribers-mlir

Author: Fabian Mora (fabianmcg)

Changes

Add requiresVisitingMutatedDefs and visitMutatedDefs methods to PromotableOpInterface. These methods allow PromotableOpInterface ops to transforms definitions mutated by a store.

This change is necessary to correctly handle the promotion of LLVM_DbgDeclareOp.


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

4 Files Affected:

  • (modified) mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td (+4-2)
  • (modified) mlir/include/mlir/Interfaces/MemorySlotInterfaces.td (+21)
  • (modified) mlir/lib/Dialect/LLVMIR/IR/LLVMMemorySlot.cpp (+12-7)
  • (modified) mlir/lib/Transforms/Mem2Reg.cpp (+13-1)
diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td
index f4bac9376f2ea0..d3857fbe06e355 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMIntrinsicOps.td
@@ -562,8 +562,10 @@ class LLVM_DbgIntrOp<string name, string argName, list<Trait> traits = []>
   }];
 }
 
-def LLVM_DbgDeclareOp : LLVM_DbgIntrOp<"dbg.declare", "addr",
-    [DeclareOpInterfaceMethods<PromotableOpInterface>]> {
+def LLVM_DbgDeclareOp : LLVM_DbgIntrOp<"dbg.declare", "addr", [
+    DeclareOpInterfaceMethods<PromotableOpInterface, [
+      "requiresVisitingMutatedDefs", "visitMutatedDefs"
+    ]>]> {
   let summary = "Describes how the address relates to a source language variable.";
   let arguments = (ins
     LLVM_AnyPointer:$addr,
diff --git a/mlir/include/mlir/Interfaces/MemorySlotInterfaces.td b/mlir/include/mlir/Interfaces/MemorySlotInterfaces.td
index e10e2d4e104c3e..af120eb059aebf 100644
--- a/mlir/include/mlir/Interfaces/MemorySlotInterfaces.td
+++ b/mlir/include/mlir/Interfaces/MemorySlotInterfaces.td
@@ -229,6 +229,27 @@ def PromotableOpInterface : OpInterface<"PromotableOpInterface"> {
       (ins "const ::llvm::SmallPtrSetImpl<mlir::OpOperand *> &":$blockingUses,
            "::mlir::RewriterBase &":$rewriter)
     >,
+    InterfaceMethod<[{
+        Checks whether the operation requires visiting the mutated
+        definitions by a store operation.
+      }], "bool", "requiresVisitingMutatedDefs", (ins), [{}],
+      [{ return false; }]
+    >,
+    InterfaceMethod<[{
+        Visits all the mutated definitions by a store operation.
+
+        This method will only be called after all blocking issues haven been
+        scheduled for removal and if `requiresVisitingMutatedDefs` returned
+        true.
+
+        The rewriter is located after the promotable operation on call. All IR
+        mutations must happen through the rewriter. During the transformation,
+        *no operation should be deleted*.
+      }],
+      "void", "visitMutatedDefs",
+      (ins "::llvm::ArrayRef<std::pair<::mlir::Operation*, ::mlir::Value>>":$mutatedDefs,
+           "::mlir::RewriterBase &":$rewriter), [{}], [{ return; }]
+    >,
   ];
 }
 
diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMMemorySlot.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMMemorySlot.cpp
index f171bf7cc4bec3..b5c5f4454dede9 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMMemorySlot.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMMemorySlot.cpp
@@ -166,13 +166,6 @@ bool LLVM::StoreOp::canUsesBeRemoved(
 DeletionKind LLVM::StoreOp::removeBlockingUses(
     const MemorySlot &slot, const SmallPtrSetImpl<OpOperand *> &blockingUses,
     RewriterBase &rewriter, Value reachingDefinition) {
-  // `canUsesBeRemoved` checked this blocking use must be the stored slot
-  // pointer.
-  for (Operation *user : slot.ptr.getUsers())
-    if (auto declareOp = dyn_cast<LLVM::DbgDeclareOp>(user))
-      rewriter.create<LLVM::DbgValueOp>(declareOp->getLoc(), getValue(),
-                                        declareOp.getVarInfo(),
-                                        declareOp.getLocationExpr());
   return DeletionKind::Delete;
 }
 
@@ -405,6 +398,18 @@ DeletionKind LLVM::DbgValueOp::removeBlockingUses(
   return DeletionKind::Keep;
 }
 
+bool LLVM::DbgDeclareOp::requiresVisitingMutatedDefs() { return true; }
+
+void LLVM::DbgDeclareOp::visitMutatedDefs(
+    ArrayRef<std::pair<Operation *, Value>> definitions,
+    RewriterBase &rewriter) {
+  for (auto [op, value] : definitions) {
+    rewriter.setInsertionPointAfter(op);
+    rewriter.create<LLVM::DbgValueOp>(getLoc(), value, getVarInfo(),
+                                      getLocationExpr());
+  }
+}
+
 //===----------------------------------------------------------------------===//
 // Interfaces for GEPOp
 //===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Transforms/Mem2Reg.cpp b/mlir/lib/Transforms/Mem2Reg.cpp
index 80e3b790163297..3aca5eb19ff5af 100644
--- a/mlir/lib/Transforms/Mem2Reg.cpp
+++ b/mlir/lib/Transforms/Mem2Reg.cpp
@@ -202,6 +202,7 @@ class MemorySlotPromoter {
   /// Contains the reaching definition at this operation. Reaching definitions
   /// are only computed for promotable memory operations with blocking uses.
   DenseMap<PromotableMemOpInterface, Value> reachingDefs;
+  DenseMap<PromotableMemOpInterface, Value> mutatedValues;
   DominanceInfo &dominance;
   MemorySlotPromotionInfo info;
   const Mem2RegStatistics &statistics;
@@ -438,6 +439,7 @@ Value MemorySlotPromoter::computeReachingDefInBlock(Block *block,
         assert(stored && "a memory operation storing to a slot must provide a "
                          "new definition of the slot");
         reachingDef = stored;
+        mutatedValues[memOp] = stored;
       }
     }
   }
@@ -552,6 +554,8 @@ void MemorySlotPromoter::removeBlockingUses() {
   dominanceSort(usersToRemoveUses, *slot.ptr.getParentBlock()->getParent());
 
   llvm::SmallVector<Operation *> toErase;
+  llvm::SmallVector<std::pair<Operation *, Value>> mutatedDefinitions;
+  llvm::SmallVector<PromotableOpInterface> visitMutatedDefinitions;
   for (Operation *toPromote : llvm::reverse(usersToRemoveUses)) {
     if (auto toPromoteMemOp = dyn_cast<PromotableMemOpInterface>(toPromote)) {
       Value reachingDef = reachingDefs.lookup(toPromoteMemOp);
@@ -565,7 +569,9 @@ void MemorySlotPromoter::removeBlockingUses() {
               slot, info.userToBlockingUses[toPromote], rewriter,
               reachingDef) == DeletionKind::Delete)
         toErase.push_back(toPromote);
-
+      if (toPromoteMemOp.storesTo(slot))
+        if (Value mutatedValue = mutatedValues[toPromoteMemOp])
+          mutatedDefinitions.push_back({toPromoteMemOp, mutatedValue});
       continue;
     }
 
@@ -574,6 +580,12 @@ void MemorySlotPromoter::removeBlockingUses() {
     if (toPromoteBasic.removeBlockingUses(info.userToBlockingUses[toPromote],
                                           rewriter) == DeletionKind::Delete)
       toErase.push_back(toPromote);
+    if (toPromoteBasic.requiresVisitingMutatedDefs())
+      visitMutatedDefinitions.push_back(toPromoteBasic);
+  }
+  for (PromotableOpInterface op : visitMutatedDefinitions) {
+    rewriter.setInsertionPointAfter(op);
+    op.visitMutatedDefs(mutatedDefinitions, rewriter);
   }
 
   for (Operation *toEraseOp : toErase)

@gysit gysit requested review from zyx-billy and Dinistro and removed request for zyx-billy March 27, 2024 12:10
Comment on lines 233 to 234
Checks whether the operation requires visiting the mutated
definitions by a store operation.
Copy link
Member

Choose a reason for hiding this comment

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

Could you explain what this means more in the documentation?

Copy link
Contributor

@Dinistro Dinistro left a comment

Choose a reason for hiding this comment

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

I do not see how this is equivalent to the existing implementation.

If I understand correctly, this change will pass all N changed definitions to each visitor-function, instead of running the visitor for each DbgValueOp separately.

Maybe add a test with two dbg value ops on the same store to determine that this works as intended.

@fabianmcg
Copy link
Contributor Author

fabianmcg commented Mar 27, 2024

Maybe add a test with two dbg value ops on the same store to determine that this works as intended.

Are you referring to a test with two dbg declare ops? Because this change should only affect llvm.dbg.declare.
Also, It's worth noting that after the change all existing tests passed.

@Dinistro
Copy link
Contributor

Maybe add a test with two dbg value ops on the same store to determine that this works as intended.

Are you referring to a test with two dbg declare ops? Because this change should only affect llvm.dbg.declare. Also, It's worth noting that after the change all existing tests passed.

Sorry for the confusion, yes, I meant two dbg declares.

I'm currently working on extensions for both SROA and Mem2Reg, and I can tell you that the test coverage is unfortunately not sufficient for such cases.

@fabianmcg
Copy link
Contributor Author

fabianmcg commented Mar 27, 2024

@Dinistro because I'm not 100% sure I understand what you are looking for in the test, are you referring to something like:

module {
  llvm.func @basic_store_load(%arg0: i64) -> i64 {
    %0 = llvm.mlir.constant(1 : i32) : i32
    %1 = llvm.alloca %0 x i64 {alignment = 8 : i64} : (i32) -> !llvm.ptr
    llvm.store %arg0, %1 {alignment = 4 : i64} : i64, !llvm.ptr
    llvm.intr.dbg.declare #di_local_variable = %1 : !llvm.ptr
    llvm.intr.dbg.declare #di_local_variable = %1 : !llvm.ptr
    %2 = llvm.load %1 {alignment = 4 : i64} : !llvm.ptr -> i64
    llvm.return %2 : i64
  }
}

WIth result:

module {
  llvm.func @basic_store_load(%arg0: i64) -> i64 {
    %0 = llvm.mlir.constant(1 : i32) : i32
    llvm.intr.dbg.value #di_local_variable = %arg0 : i64
    llvm.intr.dbg.value #di_local_variable = %arg0 : i64
    llvm.return %arg0 : i64
  }
}

@Dinistro
Copy link
Contributor

@fabianmcg yes, this is what I'm referring to. Looking at the code indicates to me that this might go wrong, due to running visitMutatedDefs for each declare op but also passing in an ArrayRef. Note that I might also just be confused 🙃

@fabianmcg
Copy link
Contributor Author

@Dinistro I added a test with two llvm.intr.dbg.declare and both versions produce the same result.

Copy link
Contributor

@Dinistro Dinistro left a comment

Choose a reason for hiding this comment

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

Oh, I was indeed confused. Thanks for adding a test for this case.

LGTM, but maybe consider to wait for @Moxinilian to confirm that this is fine for him as well.

@Moxinilian
Copy link
Member

I'd just like the doc to be a bit clearer about what this is about but otherwise looks good!

@fabianmcg fabianmcg changed the title [mlir] Add requiresVisitingMutatedDefs and visitMutatedDefs to PromotableOpInterface [mlir] Add requiresAmendingMutatedDefs and amendMutatedDefs to PromotableOpInterface Mar 27, 2024
@fabianmcg
Copy link
Contributor Author

@Moxinilian I improved the docs. I also changed the names of the methods to requiresAmendingMutatedDefs and amendMutatedDefs, as I think the previous names sparked some confusion.

@Moxinilian
Copy link
Member

I'm sorry but I still don't understand what those methods are about 😅
As a user of the interface, when I see those methods I have no idea what is happening. What does "amending the mutated definitions by a store operation" mean? What is a "mutated definition"?

@fabianmcg
Copy link
Contributor Author

fabianmcg commented Mar 28, 2024

What about:

This method returns whether the promoted operation requires amending the values produced by calls to the PromotableMemOpInterface::getStored method on the slot. Hereafter, these values are called mutated definitions by a store operation.

?

Copy link
Member

@Moxinilian Moxinilian left a comment

Choose a reason for hiding this comment

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

I think I see what you mean now. Here are some rewording suggestions, I think using simple English helps documentation clarity a lot :)

Comment on lines 233 to 234
This method returns whether the promoted operation requires amending the
mutated definitions by a store operation.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
This method returns whether the promoted operation requires amending the
mutated definitions by a store operation.
This method allows the promoted operation to visit the SSA values used
in place of the memory slot once the promotion process of the memory
slot is complete.

Comment on lines 236 to 238
If this method returns true, the operation will be visited using the
`amendMutatedDefs` method after the main mutation stage finishes
(i.e., after all ops have been processed with `removeBlockingUses`).
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
If this method returns true, the operation will be visited using the
`amendMutatedDefs` method after the main mutation stage finishes
(i.e., after all ops have been processed with `removeBlockingUses`).
If this method returns true, the `amendMutatedDefs` method on this
operation will be called after the main mutation stage finishes
(i.e., after all ops have been processed with `removeBlockingUses`).

Comment on lines 247 to 248
Transforms the IR to amend all the mutated definitions to the slot by a
store operation.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Transforms the IR to amend all the mutated definitions to the slot by a
store operation.
Transforms IR using the SSA values that replaced the memory slot.

Comment on lines +259 to +260
(ins "::llvm::ArrayRef<std::pair<::mlir::Operation*, ::mlir::Value>>":$mutatedDefs,
"::mlir::RewriterBase &":$rewriter), [{}], [{ return; }]
Copy link
Member

Choose a reason for hiding this comment

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

I feel like to be absolutely complete this method should also provide the memory slot, as one may want to know which memory slot specifically those definitions correspond to. But at the same time I am not really sure that this would be useful in practice, so change it only if you feel like it.

Operations should only request amending the mutated definitions if the
intended transformation applies to all mutated values. Furthermore,
mutated values must not be deleted.
}], "bool", "requiresAmendingMutatedDefs", (ins), [{}],
Copy link
Member

Choose a reason for hiding this comment

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

In a perfect world I'd prefer those methods to be called something like visitReplacingValues, because "amending mutated definitions" feels very vague in context.

mutations must happen through the rewriter. During the transformation,
*no operation should be deleted*.
}],
"void", "amendMutatedDefs",
Copy link
Member

Choose a reason for hiding this comment

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

Name suggestion: visitReplacedValues

*no operation should be deleted*.
}],
"void", "amendMutatedDefs",
(ins "::llvm::ArrayRef<std::pair<::mlir::Operation*, ::mlir::Value>>":$mutatedDefs,
Copy link
Member

Choose a reason for hiding this comment

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

Name suggestion: replacingValues

@@ -202,6 +202,7 @@ class MemorySlotPromoter {
/// Contains the reaching definition at this operation. Reaching definitions
/// are only computed for promotable memory operations with blocking uses.
DenseMap<PromotableMemOpInterface, Value> reachingDefs;
DenseMap<PromotableMemOpInterface, Value> mutatedValues;
Copy link
Member

Choose a reason for hiding this comment

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

I really dislike the "mutatedValues" name because it is unclear to me in what way those values are mutated. This name suggests you put something in those values when what happens conceptually is the opposite.

@@ -552,6 +554,8 @@ void MemorySlotPromoter::removeBlockingUses() {
dominanceSort(usersToRemoveUses, *slot.ptr.getParentBlock()->getParent());

llvm::SmallVector<Operation *> toErase;
llvm::SmallVector<std::pair<Operation *, Value>> mutatedDefinitions;
Copy link
Member

Choose a reason for hiding this comment

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

Same as above for this name.

@fabianmcg
Copy link
Contributor Author

@Moxinilian I followed all your suggestions but one. I changed requiresAmendingMutatedDefs to requiresReplacedValues, because I felt having visitReplacedValues and visitReplacingValues could create confusion.

Copy link
Member

@Moxinilian Moxinilian left a comment

Choose a reason for hiding this comment

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

Thank you very much for putting up with my requests ahah

@fabianmcg fabianmcg changed the title [mlir] Add requiresAmendingMutatedDefs and amendMutatedDefs to PromotableOpInterface [mlir] Add requiresReplacedValues and visitReplacedValues to PromotableOpInterface Apr 2, 2024
@Dinistro
Copy link
Contributor

Dinistro commented Apr 4, 2024

Can we land this to avoid unnecessary conflicts with potentially upcoming PRs?

@fabianmcg
Copy link
Contributor Author

fabianmcg commented Apr 4, 2024

@Moxinilian, @Dinistro are you both in favor and with high confidence on the change?

@Dinistro
Copy link
Contributor

Dinistro commented Apr 4, 2024

@Moxinilian, @Dinistro are you both in favor and with high confidence on the change?

I think this is fine, feel free to land

@Moxinilian
Copy link
Member

Same

@fabianmcg fabianmcg merged commit 220cdf9 into llvm:main Apr 4, 2024
@fabianmcg fabianmcg deleted the pr-memory-slot branch August 5, 2024 12:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
mlir:core MLIR Core Infrastructure mlir:llvm mlir
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants