Skip to content

[mlir][IR] Add InsertPoint::after(ValueRange) #114940

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

matthias-springer
Copy link
Member

@matthias-springer matthias-springer commented Nov 5, 2024

This commit adds a helper function InsertPoint::after. The function computes an insertion point that post-dominates the definition of all given values.

This functionality will be used in the merged 1:1 / 1:N dialect conversion to determine where to insert a builtin.unrealized_conversion_cast op. To ensure that the same cast can be "reused" for multiple consumer ops, the cast should be inserted as early as possible. (Alternatively, multiple cast ops could be inserted, right before each consumer op.)

TODO: Add test case.

Depends on #115587.

@llvmbot llvmbot added mlir:core MLIR Core Infrastructure mlir labels Nov 5, 2024
@llvmbot
Copy link
Member

llvmbot commented Nov 5, 2024

@llvm/pr-subscribers-mlir

Author: Matthias Springer (matthias-springer)

Changes

This commit adds a helper function to OpBuilder::InsertPoint to compute the earliest insertion point where a given list of SSA values is defined.

This functionality will be used in the merged 1:1 / 1:N dialect conversion to determine where to insert a builtin.unrealized_conversion_cast op. To ensure that the same cast can be "reused" for multiple consumer ops, the cast should be inserted as early as possible.

TODO: Add test case.

Depends on #114928.


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

2 Files Affected:

  • (modified) mlir/include/mlir/IR/Builders.h (+11)
  • (modified) mlir/lib/IR/Builders.cpp (+66)
diff --git a/mlir/include/mlir/IR/Builders.h b/mlir/include/mlir/IR/Builders.h
index 04a8bddc3cd59a..44079b4003de0c 100644
--- a/mlir/include/mlir/IR/Builders.h
+++ b/mlir/include/mlir/IR/Builders.h
@@ -334,6 +334,17 @@ class OpBuilder : public Builder {
   /// This class represents a saved insertion point.
   class InsertPoint {
   public:
+    /// Finds the closest insertion point where all given values are defined
+    /// and can be used without a dominance violation. Returns an "empty"
+    /// insertion point if there is no such insertion point.
+    static InsertPoint findClosest(ArrayRef<Value> values);
+
+    /// Creates a new insertion point right after the given value. If the given
+    /// value is a block argument, the insertion point points to the beginning
+    /// of the block. Otherwise, it points to the place right after the op that
+    /// defines the value.
+    static InsertPoint after(Value value);
+
     /// Creates a new insertion point which doesn't point to anything.
     InsertPoint() = default;
 
diff --git a/mlir/lib/IR/Builders.cpp b/mlir/lib/IR/Builders.cpp
index 5397fbabc5c95e..e610b08df71cb1 100644
--- a/mlir/lib/IR/Builders.cpp
+++ b/mlir/lib/IR/Builders.cpp
@@ -641,3 +641,69 @@ void OpBuilder::cloneRegionBefore(Region &region, Region &parent,
 void OpBuilder::cloneRegionBefore(Region &region, Block *before) {
   cloneRegionBefore(region, *before->getParent(), before->getIterator());
 }
+
+//===----------------------------------------------------------------------===//
+// InsertPoint
+//===----------------------------------------------------------------------===//
+
+OpBuilder::InsertPoint OpBuilder::InsertPoint::after(Value value) {
+  if (auto blockArg = dyn_cast<BlockArgument>(value))
+    return InsertPoint(blockArg.getOwner(), blockArg.getOwner()->begin());
+  Operation *op = value.getDefiningOp();
+  return InsertPoint(op->getBlock(), ++op->getIterator());
+}
+
+/// Helper function that returns "true" if `a` is a proper ancestor of `b`.
+static bool isAncestor(Block *a, Block *b) {
+  return a->getParentOp()->isProperAncestor(b->getParentOp());
+}
+
+OpBuilder::InsertPoint OpBuilder::InsertPoint::findClosest(ArrayRef<Value> values) {
+  // Compute the insertion point after the first value.
+  assert(!values.empty() && "expected at least one value");
+  InsertPoint result = InsertPoint::after(values.front());
+
+  // Check all other values and update the insertion point as needed.
+  for (Value v : values.drop_front()) {
+    InsertPoint pt = InsertPoint::after(v);
+
+    if (pt.getBlock() == result.getBlock()) {
+      // Both values belong to the same block. Modify the iterator (but keep
+      // the block) if needed: take the later one of the two insertion points.
+      Block *block = pt.getBlock();
+      if (pt.getPoint() == block->end()) {
+        // `pt` points to the end of the block: take `pt`.
+        result = pt;
+        continue;
+      } else if (result.getPoint() == block->end()) {
+        // `result` points to the end of the block: nothing to do.
+        continue;
+      }
+      // Neither `pt` nor `result` point to the end of the block, so both
+      // iterators point to an operation. Set `result` to the later one of the
+      // two insertion point.
+      if (result.getPoint()->isBeforeInBlock(&*pt.getPoint()))
+        result = pt;
+      continue;
+    }
+
+    if (isAncestor(result.getBlock(), pt.getBlock())) {
+      // `result` is an ancestor of `pt`. Therefore, `pt` is a valid insertion
+      // point for `v` and all previous values.
+      result = pt;
+      continue;
+    }
+
+    if (isAncestor(pt.getBlock(), result.getBlock())) {
+      // `pt` is an ancestor of `result`. Therefore, `result` is a valid
+      // insertion point for `v` and all previous values.
+      continue;
+    }
+
+    // `pt` and `result` are in different subtrees: neither is an ancestor of
+    // the other. In that case, there is no suitable insertion point.
+    return InsertPoint();
+  }
+
+  return result;
+}

@llvmbot
Copy link
Member

llvmbot commented Nov 5, 2024

@llvm/pr-subscribers-mlir-core

Author: Matthias Springer (matthias-springer)

Changes

This commit adds a helper function to OpBuilder::InsertPoint to compute the earliest insertion point where a given list of SSA values is defined.

This functionality will be used in the merged 1:1 / 1:N dialect conversion to determine where to insert a builtin.unrealized_conversion_cast op. To ensure that the same cast can be "reused" for multiple consumer ops, the cast should be inserted as early as possible.

TODO: Add test case.

Depends on #114928.


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

2 Files Affected:

  • (modified) mlir/include/mlir/IR/Builders.h (+11)
  • (modified) mlir/lib/IR/Builders.cpp (+66)
diff --git a/mlir/include/mlir/IR/Builders.h b/mlir/include/mlir/IR/Builders.h
index 04a8bddc3cd59a..44079b4003de0c 100644
--- a/mlir/include/mlir/IR/Builders.h
+++ b/mlir/include/mlir/IR/Builders.h
@@ -334,6 +334,17 @@ class OpBuilder : public Builder {
   /// This class represents a saved insertion point.
   class InsertPoint {
   public:
+    /// Finds the closest insertion point where all given values are defined
+    /// and can be used without a dominance violation. Returns an "empty"
+    /// insertion point if there is no such insertion point.
+    static InsertPoint findClosest(ArrayRef<Value> values);
+
+    /// Creates a new insertion point right after the given value. If the given
+    /// value is a block argument, the insertion point points to the beginning
+    /// of the block. Otherwise, it points to the place right after the op that
+    /// defines the value.
+    static InsertPoint after(Value value);
+
     /// Creates a new insertion point which doesn't point to anything.
     InsertPoint() = default;
 
diff --git a/mlir/lib/IR/Builders.cpp b/mlir/lib/IR/Builders.cpp
index 5397fbabc5c95e..e610b08df71cb1 100644
--- a/mlir/lib/IR/Builders.cpp
+++ b/mlir/lib/IR/Builders.cpp
@@ -641,3 +641,69 @@ void OpBuilder::cloneRegionBefore(Region &region, Region &parent,
 void OpBuilder::cloneRegionBefore(Region &region, Block *before) {
   cloneRegionBefore(region, *before->getParent(), before->getIterator());
 }
+
+//===----------------------------------------------------------------------===//
+// InsertPoint
+//===----------------------------------------------------------------------===//
+
+OpBuilder::InsertPoint OpBuilder::InsertPoint::after(Value value) {
+  if (auto blockArg = dyn_cast<BlockArgument>(value))
+    return InsertPoint(blockArg.getOwner(), blockArg.getOwner()->begin());
+  Operation *op = value.getDefiningOp();
+  return InsertPoint(op->getBlock(), ++op->getIterator());
+}
+
+/// Helper function that returns "true" if `a` is a proper ancestor of `b`.
+static bool isAncestor(Block *a, Block *b) {
+  return a->getParentOp()->isProperAncestor(b->getParentOp());
+}
+
+OpBuilder::InsertPoint OpBuilder::InsertPoint::findClosest(ArrayRef<Value> values) {
+  // Compute the insertion point after the first value.
+  assert(!values.empty() && "expected at least one value");
+  InsertPoint result = InsertPoint::after(values.front());
+
+  // Check all other values and update the insertion point as needed.
+  for (Value v : values.drop_front()) {
+    InsertPoint pt = InsertPoint::after(v);
+
+    if (pt.getBlock() == result.getBlock()) {
+      // Both values belong to the same block. Modify the iterator (but keep
+      // the block) if needed: take the later one of the two insertion points.
+      Block *block = pt.getBlock();
+      if (pt.getPoint() == block->end()) {
+        // `pt` points to the end of the block: take `pt`.
+        result = pt;
+        continue;
+      } else if (result.getPoint() == block->end()) {
+        // `result` points to the end of the block: nothing to do.
+        continue;
+      }
+      // Neither `pt` nor `result` point to the end of the block, so both
+      // iterators point to an operation. Set `result` to the later one of the
+      // two insertion point.
+      if (result.getPoint()->isBeforeInBlock(&*pt.getPoint()))
+        result = pt;
+      continue;
+    }
+
+    if (isAncestor(result.getBlock(), pt.getBlock())) {
+      // `result` is an ancestor of `pt`. Therefore, `pt` is a valid insertion
+      // point for `v` and all previous values.
+      result = pt;
+      continue;
+    }
+
+    if (isAncestor(pt.getBlock(), result.getBlock())) {
+      // `pt` is an ancestor of `result`. Therefore, `result` is a valid
+      // insertion point for `v` and all previous values.
+      continue;
+    }
+
+    // `pt` and `result` are in different subtrees: neither is an ancestor of
+    // the other. In that case, there is no suitable insertion point.
+    return InsertPoint();
+  }
+
+  return result;
+}

@matthias-springer
Copy link
Member Author

matthias-springer commented Nov 5, 2024

I could put this helper function into DialectConversion.cpp, but I thought it could be useful in general. (I'm also trying to make the 1:N dialect conversion PR smaller before sending it for review.) What's unclear is how to test this. Maybe a unit test? What do you think?

@matthias-springer matthias-springer force-pushed the users/matthias-springer/insert_pt branch from 4881106 to 08be950 Compare November 5, 2024 06:12
Copy link

github-actions bot commented Nov 5, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@matthias-springer
Copy link
Member Author

matthias-springer commented Nov 8, 2024

I'm trying to implement this in a different way: based on dominance, as you suggested. By adding a new DominanceInfo::dominates(Block*, Block::iterator, Block*, Block::iterator) function. That may also clean up other things inside of DominanceInfo. Still experimenting with the design... Marking this PR as "draft" until then.

And this will no longer depend on #114928.

@matthias-springer matthias-springer marked this pull request as draft November 8, 2024 07:42
@@ -334,6 +334,18 @@ class OpBuilder : public Builder {
/// This class represents a saved insertion point.
class InsertPoint {
public:
/// Finds the closest insertion point where all given values are defined
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's not clear to me that this is a well defined problem: what does "closest means" here?

Copy link
Member Author

@matthias-springer matthias-springer Nov 9, 2024

Choose a reason for hiding this comment

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

Possible candidates are the positions (Block::iterator) right after the definition of each value. Out of these, there should be only one unique insertion point that post-dominates all the others.

}

// `pt` and `result` are in different subtrees: neither is an ancestor of
// the other. In that case, there is no suitable insertion point.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Wouldn't the right insertion point be the post-dominator block?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. Post-dominator Block::iterator to be precise. (Adding support for that in #115587.)

@matthias-springer matthias-springer force-pushed the users/matthias-springer/insert_pt branch from 08be950 to a18a6c0 Compare November 9, 2024 11:30
@matthias-springer matthias-springer changed the base branch from users/matthias-springer/block_is_reachable to users/matthias-springer/post_dom_constant November 9, 2024 11:30
@matthias-springer matthias-springer changed the title [mlir][IR] Add helper functions to compute insertion point [mlir][IR] Add OpBuilder::setInsertionPointAfterValues Nov 9, 2024
@matthias-springer matthias-springer force-pushed the users/matthias-springer/post_dom_constant branch from caea72f to e890126 Compare November 10, 2024 06:24
@matthias-springer matthias-springer force-pushed the users/matthias-springer/insert_pt branch from a18a6c0 to f716eaa Compare November 10, 2024 06:25
@matthias-springer matthias-springer force-pushed the users/matthias-springer/post_dom_constant branch from e890126 to 943bd95 Compare November 11, 2024 02:33
Base automatically changed from users/matthias-springer/post_dom_constant to main November 11, 2024 02:43
…ation

The implementations of `DominanceInfo::properlyDominates` and `PostDominanceInfo::properlyPostDominates` are almost identical: only one line of code is different. Define the function in `DominanceInfoBase` to avoid the code duplication.

Note: This commit is not marked as NFC because `PostDominanceInfo::properlyPostDominates` now also has an `enclosingOpOk` argument.
The `properlyDominates` implementations for blocks and ops are very similar. This commit replaces them with a single implementation that operates on block iterators. That implementation can be used to implement both `properlyDominates` variants.

Note: A subsequent commit will add a new public `properlyDominates` overload that accepts block iterators. That functionality can then be used to find a valid insertion point at which a range of values is defined (by utilizing post dominance).

Depends on #115433.
@matthias-springer matthias-springer force-pushed the users/matthias-springer/insert_pt branch from f716eaa to df99520 Compare November 11, 2024 02:50
@matthias-springer matthias-springer changed the base branch from main to users/matthias-springer/block_dom November 11, 2024 02:50
@matthias-springer matthias-springer changed the title [mlir][IR] Add OpBuilder::setInsertionPointAfterValues [mlir][IR] Add InsertPoint::after(ValueRange) Nov 12, 2024
@matthias-springer matthias-springer force-pushed the users/matthias-springer/insert_pt branch from df99520 to f1dbd8a Compare November 12, 2024 04:44
/// There may be multiple suitable insertion points. This function chooses
/// an insertion right after one of the given values.
///
/// Note: Some of the given values may already have gone out of scope at the
Copy link
Member Author

Choose a reason for hiding this comment

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

Note: if there exists an insertion point that post-dominates all definitions and where all values are in scope, this function is guaranteed to find it.

@River707
Copy link
Contributor

Why don't the various transforms just use DominanceInfo? Answering these queries is what it's intended for.

Block reachability and block dominance are two different things. Just because there is a path from block A -> block B does not necessarily mean that block A dominates block B. I'm not sure how "block reachability" could be implemented with DominanceInfo.

That being said, in the dialect conversion I actually need dominance, so what I implemented here is not useful there...

Yes, my comment was geared towards the fact that several of the usages are already conflating reachability with dominance (for example the original usage in the followup IRBuilder PR). My concern is/was around the general utility of such a function, given that it cannot be used in most situations people would reach for it (i.e. dominance outside of purely structured control flow) so it wasn't clear the utility in adding it to Block. That being said I don't have an issue with this singular function, given that it's a tiny amount of graph traversal code. I would clearly mark in the comment that this function is not a replacement for dominance in any reasonable situation (given that a successor block can easily reach a predecessor in a situation such as a loop).

@matthias-springer matthias-springer force-pushed the users/matthias-springer/block_dom branch from 57bd391 to c56dc33 Compare November 12, 2024 10:59
@matthias-springer
Copy link
Member Author

Yes, my comment was geared towards the fact that several of the usages are already conflating reachability with dominance (for example the original usage in the followup IRBuilder PR). My concern is/was around the general utility of such a function, given that it cannot be used in most situations people would reach for it (i.e. dominance outside of purely structured control flow) so it wasn't clear the utility in adding it to Block. That being said I don't have an issue with this singular function, given that it's a tiny amount of graph traversal code. I would clearly mark in the comment that this function is not a replacement for dominance in any reasonable situation (given that a successor block can easily reach a predecessor in a situation such as a loop).

I think this comment is referring to #114928. I updated the comment on that PR.

@matthias-springer
Copy link
Member Author

I don't have any immediate use case for this PR anymore.

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
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants