MLIR  18.0.0git
BufferDeallocationSimplification.cpp
Go to the documentation of this file.
1 //===- BufferDeallocationSimplification.cpp -------------------------------===//
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 logic for optimizing `bufferization.dealloc` operations
10 // that requires more analysis than what can be supported by regular
11 // canonicalization patterns.
12 //
13 //===----------------------------------------------------------------------===//
14 
20 #include "mlir/IR/Matchers.h"
22 
23 namespace mlir {
24 namespace bufferization {
25 #define GEN_PASS_DEF_BUFFERDEALLOCATIONSIMPLIFICATION
26 #include "mlir/Dialect/Bufferization/Transforms/Passes.h.inc"
27 } // namespace bufferization
28 } // namespace mlir
29 
30 using namespace mlir;
31 using namespace mlir::bufferization;
32 
33 //===----------------------------------------------------------------------===//
34 // Helpers
35 //===----------------------------------------------------------------------===//
36 
37 static LogicalResult updateDeallocIfChanged(DeallocOp deallocOp,
38  ValueRange memrefs,
39  ValueRange conditions,
40  PatternRewriter &rewriter) {
41  if (deallocOp.getMemrefs() == memrefs &&
42  deallocOp.getConditions() == conditions)
43  return failure();
44 
45  rewriter.updateRootInPlace(deallocOp, [&]() {
46  deallocOp.getMemrefsMutable().assign(memrefs);
47  deallocOp.getConditionsMutable().assign(conditions);
48  });
49  return success();
50 }
51 
52 /// Given a memref value, return the "base" value by skipping over all
53 /// ViewLikeOpInterface ops (if any) in the reverse use-def chain.
54 static Value getViewBase(Value value) {
55  while (auto viewLikeOp = value.getDefiningOp<ViewLikeOpInterface>())
56  value = viewLikeOp.getViewSource();
57  return value;
58 }
59 
60 /// Return "true" if the given values are guaranteed to be different (and
61 /// non-aliasing) allocations based on the fact that one value is the result
62 /// of an allocation and the other value is a block argument of a parent block.
63 /// Note: This is a best-effort analysis that will eventually be replaced by a
64 /// proper "is same allocation" analysis. This function may return "false" even
65 /// though the two values are distinct allocations.
67  Value v1Base = getViewBase(v1);
68  Value v2Base = getViewBase(v2);
69  auto areDistinct = [](Value v1, Value v2) {
70  if (Operation *op = v1.getDefiningOp())
71  if (hasEffect<MemoryEffects::Allocate>(op, v1))
72  if (auto bbArg = dyn_cast<BlockArgument>(v2))
73  if (bbArg.getOwner()->findAncestorOpInBlock(*op))
74  return true;
75  return false;
76  };
77  return areDistinct(v1Base, v2Base) || areDistinct(v2Base, v1Base);
78 }
79 
80 /// Checks if `memref` may or must alias a MemRef in `otherList`. It is often a
81 /// requirement of optimization patterns that there cannot be any aliasing
82 /// memref in order to perform the desired simplification. The `allowSelfAlias`
83 /// argument indicates whether `memref` may be present in `otherList` which
84 /// makes this helper function applicable to situations where we already know
85 /// that `memref` is in the list but also when we don't want it in the list.
87  ValueRange otherList, Value memref,
88  bool allowSelfAlias) {
89  for (auto other : otherList) {
90  if (allowSelfAlias && other == memref)
91  continue;
92  if (distinctAllocAndBlockArgument(other, memref))
93  continue;
94  if (!analysis.alias(other, memref).isNo())
95  return true;
96  }
97  return false;
98 }
99 
100 //===----------------------------------------------------------------------===//
101 // Patterns
102 //===----------------------------------------------------------------------===//
103 
104 namespace {
105 
106 /// Remove values from the `memref` operand list that are also present in the
107 /// `retained` list (or a guaranteed alias of it) because they will never
108 /// actually be deallocated. However, we also need to be certain about which
109 /// other memrefs in the `retained` list can alias, i.e., there must not by any
110 /// may-aliasing memref. This is necessary because the `dealloc` operation is
111 /// defined to return one `i1` value per memref in the `retained` list which
112 /// represents the disjunction of the condition values corresponding to all
113 /// aliasing values in the `memref` list. In particular, this means that if
114 /// there is some value R in the `retained` list which aliases with a value M in
115 /// the `memref` list (but can only be staticaly determined to may-alias) and M
116 /// is also present in the `retained` list, then it would be illegal to remove M
117 /// because the result corresponding to R would be computed incorrectly
118 /// afterwards. Because we require an alias analysis, this pattern cannot be
119 /// applied as a regular canonicalization pattern.
120 ///
121 /// Example:
122 /// ```mlir
123 /// %0:3 = bufferization.dealloc (%m0 : ...) if (%cond0)
124 /// retain (%m0, %r0, %r1 : ...)
125 /// ```
126 /// is canonicalized to
127 /// ```mlir
128 /// // bufferization.dealloc without memrefs and conditions returns %false for
129 /// // every retained value
130 /// %0:3 = bufferization.dealloc retain (%m0, %r0, %r1 : ...)
131 /// %1 = arith.ori %0#0, %cond0 : i1
132 /// // replace %0#0 with %1
133 /// ```
134 /// given that `%r0` and `%r1` may not alias with `%m0`.
135 struct RemoveDeallocMemrefsContainedInRetained
136  : public OpRewritePattern<DeallocOp> {
137  RemoveDeallocMemrefsContainedInRetained(MLIRContext *context,
138  AliasAnalysis &aliasAnalysis)
139  : OpRewritePattern<DeallocOp>(context), aliasAnalysis(aliasAnalysis) {}
140 
141  /// The passed 'memref' must not have a may-alias relation to any retained
142  /// memref, and at least one must-alias relation. If there is no must-aliasing
143  /// memref in the retain list, we cannot simply remove the memref as there
144  /// could be situations in which it actually has to be deallocated. If it's
145  /// no-alias, then just proceed, if it's must-alias we need to update the
146  /// updated condition returned by the dealloc operation for that alias.
147  LogicalResult handleOneMemref(DeallocOp deallocOp, Value memref, Value cond,
148  PatternRewriter &rewriter) const {
149  rewriter.setInsertionPointAfter(deallocOp);
150 
151  // Check that there is no may-aliasing memref and that at least one memref
152  // in the retain list aliases (because otherwise it might have to be
153  // deallocated in some situations and can thus not be dropped).
154  bool atLeastOneMustAlias = false;
155  for (Value retained : deallocOp.getRetained()) {
156  AliasResult analysisResult = aliasAnalysis.alias(retained, memref);
157  if (analysisResult.isMay())
158  return failure();
159  if (analysisResult.isMust() || analysisResult.isPartial())
160  atLeastOneMustAlias = true;
161  }
162  if (!atLeastOneMustAlias)
163  return failure();
164 
165  // Insert arith.ori operations to update the corresponding dealloc result
166  // values to incorporate the condition of the must-aliasing memref such that
167  // we can remove that operand later on.
168  for (auto [i, retained] : llvm::enumerate(deallocOp.getRetained())) {
169  Value updatedCondition = deallocOp.getUpdatedConditions()[i];
170  AliasResult analysisResult = aliasAnalysis.alias(retained, memref);
171  if (analysisResult.isMust() || analysisResult.isPartial()) {
172  auto disjunction = rewriter.create<arith::OrIOp>(
173  deallocOp.getLoc(), updatedCondition, cond);
174  rewriter.replaceAllUsesExcept(updatedCondition, disjunction.getResult(),
175  disjunction);
176  }
177  }
178 
179  return success();
180  }
181 
182  LogicalResult matchAndRewrite(DeallocOp deallocOp,
183  PatternRewriter &rewriter) const override {
184  // There must not be any duplicates in the retain list anymore because we
185  // would miss updating one of the result values otherwise.
186  DenseSet<Value> retained(deallocOp.getRetained().begin(),
187  deallocOp.getRetained().end());
188  if (retained.size() != deallocOp.getRetained().size())
189  return failure();
190 
191  SmallVector<Value> newMemrefs, newConditions;
192  for (auto [memref, cond] :
193  llvm::zip(deallocOp.getMemrefs(), deallocOp.getConditions())) {
194 
195  if (succeeded(handleOneMemref(deallocOp, memref, cond, rewriter)))
196  continue;
197 
198  if (auto extractOp =
199  memref.getDefiningOp<memref::ExtractStridedMetadataOp>())
200  if (succeeded(handleOneMemref(deallocOp, extractOp.getOperand(), cond,
201  rewriter)))
202  continue;
203 
204  newMemrefs.push_back(memref);
205  newConditions.push_back(cond);
206  }
207 
208  // Return failure if we don't change anything such that we don't run into an
209  // infinite loop of pattern applications.
210  return updateDeallocIfChanged(deallocOp, newMemrefs, newConditions,
211  rewriter);
212  }
213 
214 private:
215  AliasAnalysis &aliasAnalysis;
216 };
217 
218 /// Remove memrefs from the `retained` list which are guaranteed to not alias
219 /// any memref in the `memrefs` list. The corresponding result value can be
220 /// replaced with `false` in that case according to the operation description.
221 ///
222 /// Example:
223 /// ```mlir
224 /// %0:2 = bufferization.dealloc (%m : memref<2xi32>) if (%cond)
225 /// retain (%r0, %r1 : memref<2xi32>, memref<2xi32>)
226 /// return %0#0, %0#1
227 /// ```
228 /// can be canonicalized to the following given that `%r0` and `%r1` do not
229 /// alias `%m`:
230 /// ```mlir
231 /// bufferization.dealloc (%m : memref<2xi32>) if (%cond)
232 /// return %false, %false
233 /// ```
234 struct RemoveRetainedMemrefsGuaranteedToNotAlias
235  : public OpRewritePattern<DeallocOp> {
236  RemoveRetainedMemrefsGuaranteedToNotAlias(MLIRContext *context,
237  AliasAnalysis &aliasAnalysis)
238  : OpRewritePattern<DeallocOp>(context), aliasAnalysis(aliasAnalysis) {}
239 
240  LogicalResult matchAndRewrite(DeallocOp deallocOp,
241  PatternRewriter &rewriter) const override {
242  SmallVector<Value> newRetainedMemrefs, replacements;
243  Value falseValue;
244  auto getOrCreateFalse = [&]() -> Value {
245  if (!falseValue)
246  falseValue = rewriter.create<arith::ConstantOp>(
247  deallocOp.getLoc(), rewriter.getBoolAttr(false));
248  return falseValue;
249  };
250 
251  for (auto retainedMemref : deallocOp.getRetained()) {
252  if (potentiallyAliasesMemref(aliasAnalysis, deallocOp.getMemrefs(),
253  retainedMemref, false)) {
254  newRetainedMemrefs.push_back(retainedMemref);
255  replacements.push_back({});
256  continue;
257  }
258 
259  replacements.push_back(getOrCreateFalse());
260  }
261 
262  if (newRetainedMemrefs.size() == deallocOp.getRetained().size())
263  return failure();
264 
265  auto newDeallocOp = rewriter.create<DeallocOp>(
266  deallocOp.getLoc(), deallocOp.getMemrefs(), deallocOp.getConditions(),
267  newRetainedMemrefs);
268  int i = 0;
269  for (auto &repl : replacements) {
270  if (!repl)
271  repl = newDeallocOp.getUpdatedConditions()[i++];
272  }
273 
274  rewriter.replaceOp(deallocOp, replacements);
275  return success();
276  }
277 
278 private:
279  AliasAnalysis &aliasAnalysis;
280 };
281 
282 /// Split off memrefs to separate dealloc operations to reduce the number of
283 /// runtime checks required and enable further canonicalization of the new and
284 /// simpler dealloc operations. A memref can be split off if it is guaranteed to
285 /// not alias with any other memref in the `memref` operand list. The results
286 /// of the old and the new dealloc operation have to be combined by computing
287 /// the element-wise disjunction of them.
288 ///
289 /// Example:
290 /// ```mlir
291 /// %0:2 = bufferization.dealloc (%m0, %m1 : memref<2xi32>, memref<2xi32>)
292 /// if (%cond0, %cond1)
293 /// retain (%r0, %r1 : memref<2xi32>, memref<2xi32>)
294 /// return %0#0, %0#1
295 /// ```
296 /// Given that `%m0` is guaranteed to never alias with `%m1`, the above IR is
297 /// canonicalized to the following, thus reducing the number of runtime alias
298 /// checks by 1 and potentially enabling further canonicalization of the new
299 /// split-up dealloc operations.
300 /// ```mlir
301 /// %0:2 = bufferization.dealloc (%m0 : memref<2xi32>) if (%cond0)
302 /// retain (%r0, %r1 : memref<2xi32>, memref<2xi32>)
303 /// %1:2 = bufferization.dealloc (%m1 : memref<2xi32>) if (%cond1)
304 /// retain (%r0, %r1 : memref<2xi32>, memref<2xi32>)
305 /// %2 = arith.ori %0#0, %1#0
306 /// %3 = arith.ori %0#1, %1#1
307 /// return %2, %3
308 /// ```
309 struct SplitDeallocWhenNotAliasingAnyOther
310  : public OpRewritePattern<DeallocOp> {
311  SplitDeallocWhenNotAliasingAnyOther(MLIRContext *context,
312  AliasAnalysis &aliasAnalysis)
313  : OpRewritePattern<DeallocOp>(context), aliasAnalysis(aliasAnalysis) {}
314 
315  LogicalResult matchAndRewrite(DeallocOp deallocOp,
316  PatternRewriter &rewriter) const override {
317  if (deallocOp.getMemrefs().size() <= 1)
318  return failure();
319 
320  SmallVector<Value> newMemrefs, newConditions, replacements;
321  DenseSet<Operation *> exceptedUsers;
322  replacements = deallocOp.getUpdatedConditions();
323  for (auto [memref, cond] :
324  llvm::zip(deallocOp.getMemrefs(), deallocOp.getConditions())) {
325  if (potentiallyAliasesMemref(aliasAnalysis, deallocOp.getMemrefs(),
326  memref, true)) {
327  newMemrefs.push_back(memref);
328  newConditions.push_back(cond);
329  continue;
330  }
331 
332  auto newDeallocOp = rewriter.create<DeallocOp>(
333  deallocOp.getLoc(), memref, cond, deallocOp.getRetained());
334  replacements = SmallVector<Value>(llvm::map_range(
335  llvm::zip(replacements, newDeallocOp.getUpdatedConditions()),
336  [&](auto replAndNew) -> Value {
337  auto orOp = rewriter.create<arith::OrIOp>(deallocOp.getLoc(),
338  std::get<0>(replAndNew),
339  std::get<1>(replAndNew));
340  exceptedUsers.insert(orOp);
341  return orOp.getResult();
342  }));
343  }
344 
345  if (newMemrefs.size() == deallocOp.getMemrefs().size())
346  return failure();
347 
348  rewriter.replaceUsesWithIf(deallocOp.getUpdatedConditions(), replacements,
349  [&](OpOperand &operand) {
350  return !exceptedUsers.contains(
351  operand.getOwner());
352  });
353  return updateDeallocIfChanged(deallocOp, newMemrefs, newConditions,
354  rewriter);
355  }
356 
357 private:
358  AliasAnalysis &aliasAnalysis;
359 };
360 
361 /// Check for every retained memref if a must-aliasing memref exists in the
362 /// 'memref' operand list with constant 'true' condition. If so, we can replace
363 /// the operation result corresponding to that retained memref with 'true'. If
364 /// this condition holds for all retained memrefs we can also remove the
365 /// aliasing memrefs and their conditions since they will never be deallocated
366 /// due to the must-alias and we don't need them to compute the result value
367 /// anymore since it got replaced with 'true'.
368 ///
369 /// Example:
370 /// ```mlir
371 /// %0:2 = bufferization.dealloc (%arg0, %arg1, %arg2 : ...)
372 /// if (%true, %true, %true)
373 /// retain (%arg0, %arg1 : memref<2xi32>, memref<2xi32>)
374 /// ```
375 /// becomes
376 /// ```mlir
377 /// %0:2 = bufferization.dealloc (%arg2 : memref<2xi32>) if (%true)
378 /// retain (%arg0, %arg1 : memref<2xi32>, memref<2xi32>)
379 /// // replace %0#0 with %true
380 /// // replace %0#1 with %true
381 /// ```
382 /// Note that the dealloc operation will still have the result values, but they
383 /// don't have uses anymore.
384 struct RetainedMemrefAliasingAlwaysDeallocatedMemref
385  : public OpRewritePattern<DeallocOp> {
386  RetainedMemrefAliasingAlwaysDeallocatedMemref(MLIRContext *context,
387  AliasAnalysis &aliasAnalysis)
388  : OpRewritePattern<DeallocOp>(context), aliasAnalysis(aliasAnalysis) {}
389 
390  LogicalResult matchAndRewrite(DeallocOp deallocOp,
391  PatternRewriter &rewriter) const override {
392  BitVector aliasesWithConstTrueMemref(deallocOp.getRetained().size());
393  SmallVector<Value> newMemrefs, newConditions;
394  for (auto [memref, cond] :
395  llvm::zip(deallocOp.getMemrefs(), deallocOp.getConditions())) {
396  bool canDropMemref = false;
397  for (auto [i, retained, res] : llvm::enumerate(
398  deallocOp.getRetained(), deallocOp.getUpdatedConditions())) {
399  if (!matchPattern(cond, m_One()))
400  continue;
401 
402  AliasResult analysisResult = aliasAnalysis.alias(retained, memref);
403  if (analysisResult.isMust() || analysisResult.isPartial()) {
404  rewriter.replaceAllUsesWith(res, cond);
405  aliasesWithConstTrueMemref[i] = true;
406  canDropMemref = true;
407  continue;
408  }
409 
410  // TODO: once our alias analysis is powerful enough we can remove the
411  // rest of this loop body
412  auto extractOp =
413  memref.getDefiningOp<memref::ExtractStridedMetadataOp>();
414  if (!extractOp)
415  continue;
416 
417  AliasResult extractAnalysisResult =
418  aliasAnalysis.alias(retained, extractOp.getOperand());
419  if (extractAnalysisResult.isMust() ||
420  extractAnalysisResult.isPartial()) {
421  rewriter.replaceAllUsesWith(res, cond);
422  aliasesWithConstTrueMemref[i] = true;
423  canDropMemref = true;
424  }
425  }
426 
427  if (!canDropMemref) {
428  newMemrefs.push_back(memref);
429  newConditions.push_back(cond);
430  }
431  }
432  if (!aliasesWithConstTrueMemref.all())
433  return failure();
434 
435  return updateDeallocIfChanged(deallocOp, newMemrefs, newConditions,
436  rewriter);
437  }
438 
439 private:
440  AliasAnalysis &aliasAnalysis;
441 };
442 
443 } // namespace
444 
445 //===----------------------------------------------------------------------===//
446 // BufferDeallocationSimplificationPass
447 //===----------------------------------------------------------------------===//
448 
449 namespace {
450 
451 /// The actual buffer deallocation pass that inserts and moves dealloc nodes
452 /// into the right positions. Furthermore, it inserts additional clones if
453 /// necessary. It uses the algorithm described at the top of the file.
454 struct BufferDeallocationSimplificationPass
455  : public bufferization::impl::BufferDeallocationSimplificationBase<
456  BufferDeallocationSimplificationPass> {
457  void runOnOperation() override {
458  AliasAnalysis &aliasAnalysis = getAnalysis<AliasAnalysis>();
459  RewritePatternSet patterns(&getContext());
460  patterns.add<RemoveDeallocMemrefsContainedInRetained,
461  RemoveRetainedMemrefsGuaranteedToNotAlias,
462  SplitDeallocWhenNotAliasingAnyOther,
463  RetainedMemrefAliasingAlwaysDeallocatedMemref>(&getContext(),
464  aliasAnalysis);
466 
467  if (failed(
468  applyPatternsAndFoldGreedily(getOperation(), std::move(patterns))))
469  signalPassFailure();
470  }
471 };
472 
473 } // namespace
474 
475 std::unique_ptr<Pass>
477  return std::make_unique<BufferDeallocationSimplificationPass>();
478 }
static bool potentiallyAliasesMemref(AliasAnalysis &analysis, ValueRange otherList, Value memref, bool allowSelfAlias)
Checks if memref may or must alias a MemRef in otherList.
static Value getViewBase(Value value)
Given a memref value, return the "base" value by skipping over all ViewLikeOpInterface ops (if any) i...
static bool distinctAllocAndBlockArgument(Value v1, Value v2)
Return "true" if the given values are guaranteed to be different (and non-aliasing) allocations based...
static LogicalResult updateDeallocIfChanged(DeallocOp deallocOp, ValueRange memrefs, ValueRange conditions, PatternRewriter &rewriter)
static MLIRContext * getContext(OpFoldResult val)
This class represents the main alias analysis interface in MLIR.
AliasResult alias(Value lhs, Value rhs)
Given two values, return their aliasing behavior.
The possible results of an alias query.
Definition: AliasAnalysis.h:26
bool isPartial() const
Returns if this result is a partial alias.
Definition: AliasAnalysis.h:68
bool isMay() const
Returns if this result is a may alias.
Definition: AliasAnalysis.h:62
bool isMust() const
Returns if this result is a must alias.
Definition: AliasAnalysis.h:65
bool isNo() const
Returns if this result indicates no possibility of aliasing.
Definition: AliasAnalysis.h:59
BoolAttr getBoolAttr(bool value)
Definition: Builders.cpp:116
MLIRContext is the top-level object for a collection of MLIR operations.
Definition: MLIRContext.h:60
Operation * create(const OperationState &state)
Creates an operation given the fields represented as an OperationState.
Definition: Builders.cpp:446
void setInsertionPointAfter(Operation *op)
Sets the insertion point to the node after the specified operation, which will cause subsequent inser...
Definition: Builders.h:397
This class represents an operand of an operation.
Definition: Value.h:263
Operation is the basic unit of execution within MLIR.
Definition: Operation.h:88
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
Definition: PatternMatch.h:727
virtual void replaceOp(Operation *op, ValueRange newValues)
This method replaces the results of the operation with the specified list of values.
void updateRootInPlace(Operation *root, CallableT &&callable)
This method is a utility wrapper around a root update of an operation.
Definition: PatternMatch.h:606
void replaceAllUsesWith(Value from, Value to)
Find uses of from and replace them with to.
Definition: PatternMatch.h:615
void replaceAllUsesExcept(Value from, Value to, Operation *exceptedUser)
Find uses of from and replace them with to except if the user is exceptedUser.
Definition: PatternMatch.h:646
void replaceUsesWithIf(Value from, Value to, function_ref< bool(OpOperand &)> functor)
Find uses of from and replace them with to if the functor returns true.
This class provides an abstraction over the different types of ranges over Values.
Definition: ValueRange.h:378
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition: Value.h:96
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition: Value.cpp:20
void populateDeallocOpCanonicalizationPatterns(RewritePatternSet &patterns, MLIRContext *context)
Add the canonicalization patterns for bufferization.dealloc to the given pattern set to make them ava...
std::unique_ptr< Pass > createBufferDeallocationSimplificationPass()
Creates a pass that optimizes bufferization.dealloc operations.
constexpr void enumerate(std::tuple< Tys... > &tuple, CallbackT &&callback)
Definition: Matchers.h:285
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
Definition: Matchers.h:401
LogicalResult failure(bool isFailure=true)
Utility function to generate a LogicalResult.
Definition: LogicalResult.h:62
bool succeeded(LogicalResult result)
Utility function that returns true if the provided LogicalResult corresponds to a success value.
Definition: LogicalResult.h:68
LogicalResult success(bool isSuccess=true)
Utility function to generate a LogicalResult.
Definition: LogicalResult.h:56
detail::constant_int_predicate_matcher m_One()
Matches a constant scalar / vector splat / tensor splat integer one.
Definition: Matchers.h:389
LogicalResult applyPatternsAndFoldGreedily(Region &region, const FrozenRewritePatternSet &patterns, GreedyRewriteConfig config=GreedyRewriteConfig(), bool *changed=nullptr)
Rewrite ops in the given region, which must be isolated from above, by repeatedly applying the highes...
bool failed(LogicalResult result)
Utility function that returns true if the provided LogicalResult corresponds to a failure value.
Definition: LogicalResult.h:72
This class represents an efficient way to signal success or failure.
Definition: LogicalResult.h:26
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...
Definition: PatternMatch.h:357