MLIR  18.0.0git
Hoisting.cpp
Go to the documentation of this file.
1 //===- Hoisting.cpp - Linalg hoisting transformations ---------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // This file implements functions concerned with hoisting invariant operations
10 // in the context of Linalg transformations.
11 //
12 //===----------------------------------------------------------------------===//
13 
29 #include "mlir/IR/BuiltinOps.h"
30 #include "mlir/IR/Dominance.h"
33 #include "llvm/ADT/StringRef.h"
34 #include "llvm/ADT/TypeSwitch.h"
35 #include "llvm/Support/Debug.h"
36 
37 using llvm::dbgs;
38 
39 #define DEBUG_TYPE "linalg-hoisting"
40 
41 #define DBGS() (dbgs() << '[' << DEBUG_TYPE << "] ")
42 
43 using namespace mlir;
44 using namespace mlir::linalg;
45 
46 static bool noAliasingUseInLoop(vector::TransferReadOp transferRead,
47  LoopLikeOpInterface loop) {
48  Value source = transferRead.getSource();
49 
50  // Skip view-like Ops and retrive the actual soruce Operation
51  while (auto srcOp =
52  dyn_cast_or_null<ViewLikeOpInterface>(source.getDefiningOp()))
53  source = srcOp.getViewSource();
54 
55  llvm::SmallVector<Operation *, 32> users(source.getUsers().begin(),
56  source.getUsers().end());
57  llvm::SmallDenseSet<Operation *, 32> processed;
58  while (!users.empty()) {
59  Operation *user = users.pop_back_val();
60  // If the user has already been processed skip.
61  if (!processed.insert(user).second)
62  continue;
63  if (auto viewLike = dyn_cast<ViewLikeOpInterface>(user)) {
64  users.append(viewLike->getUsers().begin(), viewLike->getUsers().end());
65  continue;
66  }
67  if (isMemoryEffectFree(user) || isa<vector::TransferReadOp>(user))
68  continue;
69  if (!loop->isAncestor(user))
70  continue;
71  return false;
72  }
73  return true;
74 }
75 
77  bool changed = true;
78  while (changed) {
79  changed = false;
80  // First move loop invariant ops outside of their loop. This needs to be
81  // done before as we cannot move ops without interrupting the function walk.
82  func.walk(
83  [&](LoopLikeOpInterface loopLike) { moveLoopInvariantCode(loopLike); });
84 
85  func.walk([&](vector::TransferReadOp transferRead) {
86  if (!isa<MemRefType>(transferRead.getShapedType()))
87  return WalkResult::advance();
88 
89  LLVM_DEBUG(DBGS() << "Candidate for hoisting: "
90  << *transferRead.getOperation() << "\n");
91  auto loop = dyn_cast<LoopLikeOpInterface>(transferRead->getParentOp());
92  LLVM_DEBUG(DBGS() << "Parent op: " << *transferRead->getParentOp()
93  << "\n");
94  if (!isa_and_nonnull<scf::ForOp, affine::AffineForOp>(loop))
95  return WalkResult::advance();
96 
97  LLVM_DEBUG(DBGS() << "Candidate read: " << *transferRead.getOperation()
98  << "\n");
99 
100  SetVector<Operation *> forwardSlice;
101  getForwardSlice(transferRead.getOperation(), &forwardSlice);
102 
103  // Look for the last TransferWriteOp in the forwardSlice of
104  // `transferRead` that operates on the same memref.
105  vector::TransferWriteOp transferWrite;
106  for (auto *sliceOp : llvm::reverse(forwardSlice)) {
107  auto candidateWrite = dyn_cast<vector::TransferWriteOp>(sliceOp);
108  if (!candidateWrite ||
109  candidateWrite.getSource() != transferRead.getSource())
110  continue;
111  transferWrite = candidateWrite;
112  }
113 
114  // All operands of the TransferRead must be defined outside of the loop.
115  for (auto operand : transferRead.getOperands())
116  if (!loop.isDefinedOutsideOfLoop(operand))
117  return WalkResult::advance();
118 
119  // Only hoist transfer_read / transfer_write pairs and singleton
120  // transfer_reads for now.
121  if (!transferWrite) {
122  // Make sure there are no other accesses to the memref before
123  // hoisting transfer_read.
124  if (noAliasingUseInLoop(transferRead, loop))
125  loop.moveOutOfLoop(transferRead);
126  return WalkResult::advance();
127  }
128 
129  LLVM_DEBUG(DBGS() << "Candidate: " << *transferWrite.getOperation()
130  << "\n");
131 
132  // Approximate aliasing by checking that:
133  // 1. indices, vector type and permutation map are the same (i.e., the
134  // transfer_read/transfer_write ops are matching),
135  // 2. source operands for transfer.{read|write} do not originate from
136  // Ops implementing ViewLikeOpInterface.
137  // 3. no other operations in the loop access the same memref except
138  // for transfer_read/transfer_write accessing statically disjoint
139  // slices.
140  if (transferRead.getIndices() != transferWrite.getIndices() ||
141  transferRead.getVectorType() != transferWrite.getVectorType() ||
142  transferRead.getPermutationMap() != transferWrite.getPermutationMap())
143  return WalkResult::advance();
144 
145  auto *source = transferRead.getSource().getDefiningOp();
146  if (source && isa_and_nonnull<ViewLikeOpInterface>(source))
147  return WalkResult::advance();
148 
149  source = transferWrite.getSource().getDefiningOp();
150  if (source && isa_and_nonnull<ViewLikeOpInterface>(source))
151  return WalkResult::advance();
152 
153  // TODO: may want to memoize this information for performance but it
154  // likely gets invalidated often.
155  DominanceInfo dom(loop);
156  if (!dom.properlyDominates(transferRead.getOperation(), transferWrite))
157  return WalkResult::advance();
158  for (auto &use : transferRead.getSource().getUses()) {
159  if (!loop->isAncestor(use.getOwner()))
160  continue;
161  if (use.getOwner() == transferRead.getOperation() ||
162  use.getOwner() == transferWrite.getOperation())
163  continue;
164  if (auto transferWriteUse =
165  dyn_cast<vector::TransferWriteOp>(use.getOwner())) {
167  cast<VectorTransferOpInterface>(*transferWrite),
168  cast<VectorTransferOpInterface>(*transferWriteUse),
169  /*testDynamicValueUsingBounds=*/true))
170  return WalkResult::advance();
171  } else if (auto transferReadUse =
172  dyn_cast<vector::TransferReadOp>(use.getOwner())) {
174  cast<VectorTransferOpInterface>(*transferWrite),
175  cast<VectorTransferOpInterface>(*transferReadUse),
176  /*testDynamicValueUsingBounds=*/true))
177  return WalkResult::advance();
178  } else {
179  // Unknown use, we cannot prove that it doesn't alias with the
180  // transferRead/transferWrite operations.
181  return WalkResult::advance();
182  }
183  }
184 
185  // Hoist read before.
186  loop.moveOutOfLoop(transferRead);
187 
188  // Hoist write after.
189  transferWrite->moveAfter(loop);
190 
191  // Rewrite `loop` with new yields by cloning and erase the original loop.
192  IRRewriter rewriter(transferRead.getContext());
193  NewYieldValuesFn yieldFn = [&](OpBuilder &b, Location loc,
194  ArrayRef<BlockArgument> newBBArgs) {
195  return SmallVector<Value>{transferWrite.getVector()};
196  };
197 
198  auto maybeNewLoop = loop.replaceWithAdditionalYields(
199  rewriter, transferRead.getVector(),
200  /*replaceInitOperandUsesInLoop=*/true, yieldFn);
201  if (failed(maybeNewLoop))
202  return WalkResult::interrupt();
203 
204  transferWrite.getVectorMutable().assign(
205  maybeNewLoop->getOperation()->getResults().back());
206  changed = true;
207  // Need to interrupt and restart because erasing the loop messes up
208  // the walk.
209  return WalkResult::interrupt();
210  });
211  }
212 }
static bool noAliasingUseInLoop(vector::TransferReadOp transferRead, LoopLikeOpInterface loop)
Definition: Hoisting.cpp:46
#define DBGS()
Definition: Hoisting.cpp:41
A class for computing basic dominance information.
Definition: Dominance.h:121
bool properlyDominates(Operation *a, Operation *b, bool enclosingOpOk=true) const
Return true if operation A properly dominates operation B, i.e.
Definition: Dominance.h:134
This class coordinates rewriting a piece of IR outside of a pattern rewrite, providing a way to keep ...
Definition: PatternMatch.h:710
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition: Location.h:63
This class helps build Operations.
Definition: Builders.h:206
Operation is the basic unit of execution within MLIR.
Definition: Operation.h:88
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition: Value.h:96
user_range getUsers() const
Definition: Value.h:224
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition: Value.cpp:20
static WalkResult advance()
Definition: Visitors.h:52
static WalkResult interrupt()
Definition: Visitors.h:51
void hoistRedundantVectorTransfers(func::FuncOp func)
Hoist vector.transfer_read/vector.transfer_write on buffers pairs out of immediately enclosing scf::F...
Definition: Hoisting.cpp:76
bool isDisjointTransferSet(VectorTransferOpInterface transferA, VectorTransferOpInterface transferB, bool testDynamicValueUsingBounds=false)
Return true if we can prove that the transfer operations access disjoint memory, requiring the operat...
Definition: VectorOps.cpp:251
Include the generated interface declarations.
bool isMemoryEffectFree(Operation *op)
Returns true if the given operation is free of memory effects.
std::function< SmallVector< Value >(OpBuilder &b, Location loc, ArrayRef< BlockArgument > newBbArgs)> NewYieldValuesFn
A function that returns the additional yielded values during replaceWithAdditionalYields.
bool failed(LogicalResult result)
Utility function that returns true if the provided LogicalResult corresponds to a failure value.
Definition: LogicalResult.h:72
size_t moveLoopInvariantCode(ArrayRef< Region * > regions, function_ref< bool(Value, Region *)> isDefinedOutsideRegion, function_ref< bool(Operation *, Region *)> shouldMoveOutOfRegion, function_ref< void(Operation *, Region *)> moveOutOfRegion)
Given a list of regions, perform loop-invariant code motion.
void getForwardSlice(Operation *op, SetVector< Operation * > *forwardSlice, const ForwardSliceOptions &options={})
Fills forwardSlice with the computed forward slice (i.e.