1 //===- GreedyPatternRewriteDriver.cpp - A greedy rewriter -----------------===//
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 mlir::applyPatternsAndFoldGreedily.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "mlir/Transforms/GreedyPatternRewriteDriver.h"
14 #include "mlir/Interfaces/SideEffectInterfaces.h"
15 #include "mlir/Rewrite/PatternApplicator.h"
16 #include "mlir/Transforms/FoldUtils.h"
17 #include "mlir/Transforms/RegionUtils.h"
18 #include "llvm/ADT/DenseMap.h"
19 #include "llvm/Support/CommandLine.h"
20 #include "llvm/Support/Debug.h"
21 #include "llvm/Support/ScopedPrinter.h"
22 #include "llvm/Support/raw_ostream.h"
23 
24 using namespace mlir;
25 
26 #define DEBUG_TYPE "greedy-rewriter"
27 
28 //===----------------------------------------------------------------------===//
29 // GreedyPatternRewriteDriver
30 //===----------------------------------------------------------------------===//
31 
32 namespace {
33 /// This is a worklist-driven driver for the PatternMatcher, which repeatedly
34 /// applies the locally optimal patterns in a roughly "bottom up" way.
35 class GreedyPatternRewriteDriver : public PatternRewriter {
36 public:
37   explicit GreedyPatternRewriteDriver(MLIRContext *ctx,
38                                       const FrozenRewritePatternSet &patterns,
39                                       const GreedyRewriteConfig &config);
40 
41   /// Simplify the operations within the given regions.
42   bool simplify(MutableArrayRef<Region> regions);
43 
44   /// Add the given operation to the worklist.
45   void addToWorklist(Operation *op);
46 
47   /// Pop the next operation from the worklist.
48   Operation *popFromWorklist();
49 
50   /// If the specified operation is in the worklist, remove it.
51   void removeFromWorklist(Operation *op);
52 
53 protected:
54   // Implement the hook for inserting operations, and make sure that newly
55   // inserted ops are added to the worklist for processing.
56   void notifyOperationInserted(Operation *op) override;
57 
58   // Look over the provided operands for any defining operations that should
59   // be re-added to the worklist. This function should be called when an
60   // operation is modified or removed, as it may trigger further
61   // simplifications.
62   template <typename Operands>
63   void addToWorklist(Operands &&operands);
64 
65   // If an operation is about to be removed, make sure it is not in our
66   // worklist anymore because we'd get dangling references to it.
67   void notifyOperationRemoved(Operation *op) override;
68 
69   // When the root of a pattern is about to be replaced, it can trigger
70   // simplifications to its users - make sure to add them to the worklist
71   // before the root is changed.
72   void notifyRootReplaced(Operation *op) override;
73 
74   /// PatternRewriter hook for erasing a dead operation.
75   void eraseOp(Operation *op) override;
76 
77   /// PatternRewriter hook for notifying match failure reasons.
78   LogicalResult
79   notifyMatchFailure(Operation *op,
80                      function_ref<void(Diagnostic &)> reasonCallback) override;
81 
82   /// The low-level pattern applicator.
83   PatternApplicator matcher;
84 
85   /// The worklist for this transformation keeps track of the operations that
86   /// need to be revisited, plus their index in the worklist.  This allows us to
87   /// efficiently remove operations from the worklist when they are erased, even
88   /// if they aren't the root of a pattern.
89   std::vector<Operation *> worklist;
90   DenseMap<Operation *, unsigned> worklistMap;
91 
92   /// Non-pattern based folder for operations.
93   OperationFolder folder;
94 
95 private:
96   /// Configuration information for how to simplify.
97   GreedyRewriteConfig config;
98 
99 #ifndef NDEBUG
100   /// A logger used to emit information during the application process.
101   llvm::ScopedPrinter logger{llvm::dbgs()};
102 #endif
103 };
104 } // namespace
105 
106 GreedyPatternRewriteDriver::GreedyPatternRewriteDriver(
107     MLIRContext *ctx, const FrozenRewritePatternSet &patterns,
108     const GreedyRewriteConfig &config)
109     : PatternRewriter(ctx), matcher(patterns), folder(ctx), config(config) {
110   worklist.reserve(64);
111 
112   // Apply a simple cost model based solely on pattern benefit.
113   matcher.applyDefaultCostModel();
114 }
115 
116 bool GreedyPatternRewriteDriver::simplify(MutableArrayRef<Region> regions) {
117 #ifndef NDEBUG
118   const char *logLineComment =
119       "//===-------------------------------------------===//\n";
120 
121   /// A utility function to log a process result for the given reason.
122   auto logResult = [&](StringRef result, const llvm::Twine &msg = {}) {
123     logger.unindent();
124     logger.startLine() << "} -> " << result;
125     if (!msg.isTriviallyEmpty())
126       logger.getOStream() << " : " << msg;
127     logger.getOStream() << "\n";
128   };
129   auto logResultWithLine = [&](StringRef result, const llvm::Twine &msg = {}) {
130     logResult(result, msg);
131     logger.startLine() << logLineComment;
132   };
133 #endif
134 
135   bool changed = false;
136   unsigned iteration = 0;
137   do {
138     worklist.clear();
139     worklistMap.clear();
140 
141     if (!config.useTopDownTraversal) {
142       // Add operations to the worklist in postorder.
143       for (auto &region : regions)
144         region.walk([this](Operation *op) { addToWorklist(op); });
145     } else {
146       // Add all nested operations to the worklist in preorder.
147       for (auto &region : regions)
148         region.walk<WalkOrder::PreOrder>(
149             [this](Operation *op) { worklist.push_back(op); });
150 
151       // Reverse the list so our pop-back loop processes them in-order.
152       std::reverse(worklist.begin(), worklist.end());
153       // Remember the reverse index.
154       for (size_t i = 0, e = worklist.size(); i != e; ++i)
155         worklistMap[worklist[i]] = i;
156     }
157 
158     // These are scratch vectors used in the folding loop below.
159     SmallVector<Value, 8> originalOperands, resultValues;
160 
161     changed = false;
162     while (!worklist.empty()) {
163       auto *op = popFromWorklist();
164 
165       // Nulls get added to the worklist when operations are removed, ignore
166       // them.
167       if (op == nullptr)
168         continue;
169 
170       LLVM_DEBUG({
171         logger.getOStream() << "\n";
172         logger.startLine() << logLineComment;
173         logger.startLine() << "Processing operation : '" << op->getName()
174                            << "'(" << op << ") {\n";
175         logger.indent();
176 
177         // If the operation has no regions, just print it here.
178         if (op->getNumRegions() == 0) {
179           op->print(
180               logger.startLine(),
181               OpPrintingFlags().printGenericOpForm().elideLargeElementsAttrs());
182           logger.getOStream() << "\n\n";
183         }
184       });
185 
186       // If the operation is trivially dead - remove it.
187       if (isOpTriviallyDead(op)) {
188         notifyOperationRemoved(op);
189         op->erase();
190         changed = true;
191 
192         LLVM_DEBUG(logResultWithLine("success", "operation is trivially dead"));
193         continue;
194       }
195 
196       // Collects all the operands and result uses of the given `op` into work
197       // list. Also remove `op` and nested ops from worklist.
198       originalOperands.assign(op->operand_begin(), op->operand_end());
199       auto preReplaceAction = [&](Operation *op) {
200         // Add the operands to the worklist for visitation.
201         addToWorklist(originalOperands);
202 
203         // Add all the users of the result to the worklist so we make sure
204         // to revisit them.
205         for (auto result : op->getResults())
206           for (auto *userOp : result.getUsers())
207             addToWorklist(userOp);
208 
209         notifyOperationRemoved(op);
210       };
211 
212       // Add the given operation to the worklist.
213       auto collectOps = [this](Operation *op) { addToWorklist(op); };
214 
215       // Try to fold this op.
216       bool inPlaceUpdate;
217       if ((succeeded(folder.tryToFold(op, collectOps, preReplaceAction,
218                                       &inPlaceUpdate)))) {
219         LLVM_DEBUG(logResultWithLine("success", "operation was folded"));
220 
221         changed = true;
222         if (!inPlaceUpdate)
223           continue;
224       }
225 
226       // Try to match one of the patterns. The rewriter is automatically
227       // notified of any necessary changes, so there is nothing else to do
228       // here.
229 #ifndef NDEBUG
230       auto canApply = [&](const Pattern &pattern) {
231         LLVM_DEBUG({
232           logger.getOStream() << "\n";
233           logger.startLine() << "* Pattern " << pattern.getDebugName() << " : '"
234                              << op->getName() << " -> (";
235           llvm::interleaveComma(pattern.getGeneratedOps(), logger.getOStream());
236           logger.getOStream() << ")' {\n";
237           logger.indent();
238         });
239         return true;
240       };
241       auto onFailure = [&](const Pattern &pattern) {
242         LLVM_DEBUG(logResult("failure", "pattern failed to match"));
243       };
244       auto onSuccess = [&](const Pattern &pattern) {
245         LLVM_DEBUG(logResult("success", "pattern applied successfully"));
246         return success();
247       };
248 
249       LogicalResult matchResult =
250           matcher.matchAndRewrite(op, *this, canApply, onFailure, onSuccess);
251       if (succeeded(matchResult))
252         LLVM_DEBUG(logResultWithLine("success", "pattern matched"));
253       else
254         LLVM_DEBUG(logResultWithLine("failure", "pattern failed to match"));
255 #else
256       LogicalResult matchResult = matcher.matchAndRewrite(op, *this);
257 #endif
258 
259 
260 #ifndef NDEBUG
261 #endif
262 
263       changed |= succeeded(matchResult);
264     }
265 
266     // After applying patterns, make sure that the CFG of each of the regions
267     // is kept up to date.
268     if (config.enableRegionSimplification)
269       changed |= succeeded(simplifyRegions(*this, regions));
270   } while (changed &&
271            (++iteration < config.maxIterations ||
272             config.maxIterations == GreedyRewriteConfig::kNoIterationLimit));
273 
274   // Whether the rewrite converges, i.e. wasn't changed in the last iteration.
275   return !changed;
276 }
277 
278 void GreedyPatternRewriteDriver::addToWorklist(Operation *op) {
279   // Check to see if the worklist already contains this op.
280   if (worklistMap.count(op))
281     return;
282 
283   worklistMap[op] = worklist.size();
284   worklist.push_back(op);
285 }
286 
287 Operation *GreedyPatternRewriteDriver::popFromWorklist() {
288   auto *op = worklist.back();
289   worklist.pop_back();
290 
291   // This operation is no longer in the worklist, keep worklistMap up to date.
292   if (op)
293     worklistMap.erase(op);
294   return op;
295 }
296 
297 void GreedyPatternRewriteDriver::removeFromWorklist(Operation *op) {
298   auto it = worklistMap.find(op);
299   if (it != worklistMap.end()) {
300     assert(worklist[it->second] == op && "malformed worklist data structure");
301     worklist[it->second] = nullptr;
302     worklistMap.erase(it);
303   }
304 }
305 
306 void GreedyPatternRewriteDriver::notifyOperationInserted(Operation *op) {
307   LLVM_DEBUG({
308     logger.startLine() << "** Insert  : '" << op->getName() << "'(" << op
309                        << ")\n";
310   });
311   addToWorklist(op);
312 }
313 
314 template <typename Operands>
315 void GreedyPatternRewriteDriver::addToWorklist(Operands &&operands) {
316   for (Value operand : operands) {
317     // If the use count of this operand is now < 2, we re-add the defining
318     // operation to the worklist.
319     // TODO: This is based on the fact that zero use operations
320     // may be deleted, and that single use values often have more
321     // canonicalization opportunities.
322     if (!operand || (!operand.use_empty() && !operand.hasOneUse()))
323       continue;
324     if (auto *defOp = operand.getDefiningOp())
325       addToWorklist(defOp);
326   }
327 }
328 
329 void GreedyPatternRewriteDriver::notifyOperationRemoved(Operation *op) {
330   addToWorklist(op->getOperands());
331   op->walk([this](Operation *operation) {
332     removeFromWorklist(operation);
333     folder.notifyRemoval(operation);
334   });
335 }
336 
337 void GreedyPatternRewriteDriver::notifyRootReplaced(Operation *op) {
338   LLVM_DEBUG({
339     logger.startLine() << "** Replace : '" << op->getName() << "'(" << op
340                        << ")\n";
341   });
342   for (auto result : op->getResults())
343     for (auto *user : result.getUsers())
344       addToWorklist(user);
345 }
346 
347 void GreedyPatternRewriteDriver::eraseOp(Operation *op) {
348   LLVM_DEBUG({
349     logger.startLine() << "** Erase   : '" << op->getName() << "'(" << op
350                        << ")\n";
351   });
352   PatternRewriter::eraseOp(op);
353 }
354 
355 LogicalResult GreedyPatternRewriteDriver::notifyMatchFailure(
356     Operation *op, function_ref<void(Diagnostic &)> reasonCallback) {
357   LLVM_DEBUG({
358     Diagnostic diag(op->getLoc(), DiagnosticSeverity::Remark);
359     reasonCallback(diag);
360     logger.startLine() << "** Failure : " << diag.str() << "\n";
361   });
362   return failure();
363 }
364 
365 /// Rewrite the regions of the specified operation, which must be isolated from
366 /// above, by repeatedly applying the highest benefit patterns in a greedy
367 /// work-list driven manner. Return success if no more patterns can be matched
368 /// in the result operation regions. Note: This does not apply patterns to the
369 /// top-level operation itself.
370 ///
371 LogicalResult
372 mlir::applyPatternsAndFoldGreedily(MutableArrayRef<Region> regions,
373                                    const FrozenRewritePatternSet &patterns,
374                                    GreedyRewriteConfig config) {
375   if (regions.empty())
376     return success();
377 
378   // The top-level operation must be known to be isolated from above to
379   // prevent performing canonicalizations on operations defined at or above
380   // the region containing 'op'.
381   auto regionIsIsolated = [](Region &region) {
382     return region.getParentOp()->hasTrait<OpTrait::IsIsolatedFromAbove>();
383   };
384   (void)regionIsIsolated;
385   assert(llvm::all_of(regions, regionIsIsolated) &&
386          "patterns can only be applied to operations IsolatedFromAbove");
387 
388   // Start the pattern driver.
389   GreedyPatternRewriteDriver driver(regions[0].getContext(), patterns, config);
390   bool converged = driver.simplify(regions);
391   LLVM_DEBUG(if (!converged) {
392     llvm::dbgs() << "The pattern rewrite doesn't converge after scanning "
393                  << config.maxIterations << " times\n";
394   });
395   return success(converged);
396 }
397 
398 //===----------------------------------------------------------------------===//
399 // OpPatternRewriteDriver
400 //===----------------------------------------------------------------------===//
401 
402 namespace {
403 /// This is a simple driver for the PatternMatcher to apply patterns and perform
404 /// folding on a single op. It repeatedly applies locally optimal patterns.
405 class OpPatternRewriteDriver : public PatternRewriter {
406 public:
407   explicit OpPatternRewriteDriver(MLIRContext *ctx,
408                                   const FrozenRewritePatternSet &patterns)
409       : PatternRewriter(ctx), matcher(patterns), folder(ctx) {
410     // Apply a simple cost model based solely on pattern benefit.
411     matcher.applyDefaultCostModel();
412   }
413 
414   LogicalResult simplifyLocally(Operation *op, int maxIterations, bool &erased);
415 
416   // These are hooks implemented for PatternRewriter.
417 protected:
418   /// If an operation is about to be removed, mark it so that we can let clients
419   /// know.
420   void notifyOperationRemoved(Operation *op) override {
421     opErasedViaPatternRewrites = true;
422   }
423 
424   // When a root is going to be replaced, its removal will be notified as well.
425   // So there is nothing to do here.
426   void notifyRootReplaced(Operation *op) override {}
427 
428 private:
429   /// The low-level pattern applicator.
430   PatternApplicator matcher;
431 
432   /// Non-pattern based folder for operations.
433   OperationFolder folder;
434 
435   /// Set to true if the operation has been erased via pattern rewrites.
436   bool opErasedViaPatternRewrites = false;
437 };
438 
439 } // namespace
440 
441 /// Performs the rewrites and folding only on `op`. The simplification
442 /// converges if the op is erased as a result of being folded, replaced, or
443 /// becoming dead, or no more changes happen in an iteration. Returns success if
444 /// the rewrite converges in `maxIterations`. `erased` is set to true if `op`
445 /// gets erased.
446 LogicalResult OpPatternRewriteDriver::simplifyLocally(Operation *op,
447                                                       int maxIterations,
448                                                       bool &erased) {
449   bool changed = false;
450   erased = false;
451   opErasedViaPatternRewrites = false;
452   int iterations = 0;
453   // Iterate until convergence or until maxIterations. Deletion of the op as
454   // a result of being dead or folded is convergence.
455   do {
456     changed = false;
457 
458     // If the operation is trivially dead - remove it.
459     if (isOpTriviallyDead(op)) {
460       op->erase();
461       erased = true;
462       return success();
463     }
464 
465     // Try to fold this op.
466     bool inPlaceUpdate;
467     if (succeeded(folder.tryToFold(op, /*processGeneratedConstants=*/nullptr,
468                                    /*preReplaceAction=*/nullptr,
469                                    &inPlaceUpdate))) {
470       changed = true;
471       if (!inPlaceUpdate) {
472         erased = true;
473         return success();
474       }
475     }
476 
477     // Try to match one of the patterns. The rewriter is automatically
478     // notified of any necessary changes, so there is nothing else to do here.
479     changed |= succeeded(matcher.matchAndRewrite(op, *this));
480     if ((erased = opErasedViaPatternRewrites))
481       return success();
482   } while (changed &&
483            (++iterations < maxIterations ||
484             maxIterations == GreedyRewriteConfig::kNoIterationLimit));
485 
486   // Whether the rewrite converges, i.e. wasn't changed in the last iteration.
487   return failure(changed);
488 }
489 
490 //===----------------------------------------------------------------------===//
491 // MultiOpPatternRewriteDriver
492 //===----------------------------------------------------------------------===//
493 
494 namespace {
495 
496 /// This is a specialized GreedyPatternRewriteDriver to apply patterns and
497 /// perform folding for a supplied set of ops. It repeatedly simplifies while
498 /// restricting the rewrites to only the provided set of ops or optionally
499 /// to those directly affected by it (result users or operand providers).
500 class MultiOpPatternRewriteDriver : public GreedyPatternRewriteDriver {
501 public:
502   explicit MultiOpPatternRewriteDriver(MLIRContext *ctx,
503                                        const FrozenRewritePatternSet &patterns,
504                                        bool strict)
505       : GreedyPatternRewriteDriver(ctx, patterns, GreedyRewriteConfig()),
506         strictMode(strict) {}
507 
508   bool simplifyLocally(ArrayRef<Operation *> op);
509 
510 private:
511   // Look over the provided operands for any defining operations that should
512   // be re-added to the worklist. This function should be called when an
513   // operation is modified or removed, as it may trigger further
514   // simplifications. If `strict` is set to true, only ops in
515   // `strictModeFilteredOps` are considered.
516   template <typename Operands>
517   void addOperandsToWorklist(Operands &&operands) {
518     for (Value operand : operands) {
519       if (auto *defOp = operand.getDefiningOp()) {
520         if (!strictMode || strictModeFilteredOps.contains(defOp))
521           addToWorklist(defOp);
522       }
523     }
524   }
525 
526   void notifyOperationRemoved(Operation *op) override {
527     GreedyPatternRewriteDriver::notifyOperationRemoved(op);
528     if (strictMode)
529       strictModeFilteredOps.erase(op);
530   }
531 
532   /// If `strictMode` is true, any pre-existing ops outside of
533   /// `strictModeFilteredOps` remain completely untouched by the rewrite driver.
534   /// If `strictMode` is false, operations that use results of (or supply
535   /// operands to) any rewritten ops stemming from the simplification of the
536   /// provided ops are in turn simplified; any other ops still remain untouched
537   /// (i.e., regardless of `strictMode`).
538   bool strictMode = false;
539 
540   /// The list of ops we are restricting our rewrites to if `strictMode` is on.
541   /// These include the supplied set of ops as well as new ops created while
542   /// rewriting those ops. This set is not maintained when strictMode is off.
543   llvm::SmallDenseSet<Operation *, 4> strictModeFilteredOps;
544 };
545 
546 } // namespace
547 
548 /// Performs the specified rewrites on `ops` while also trying to fold these ops
549 /// as well as any other ops that were in turn created due to these rewrite
550 /// patterns. Any pre-existing ops outside of `ops` remain completely
551 /// unmodified if `strictMode` is true. If `strictMode` is false, other
552 /// operations that use results of rewritten ops or supply operands to such ops
553 /// are in turn simplified; any other ops still remain unmodified (i.e.,
554 /// regardless of `strictMode`). Note that ops in `ops` could be erased as a
555 /// result of folding, becoming dead, or via pattern rewrites. Returns true if
556 /// at all any changes happened.
557 // Unlike `OpPatternRewriteDriver::simplifyLocally` which works on a single op
558 // or GreedyPatternRewriteDriver::simplify, this method just iterates until
559 // the worklist is empty. As our objective is to keep simplification "local",
560 // there is no strong rationale to re-add all operations into the worklist and
561 // rerun until an iteration changes nothing. If more widereaching simplification
562 // is desired, GreedyPatternRewriteDriver should be used.
563 bool MultiOpPatternRewriteDriver::simplifyLocally(ArrayRef<Operation *> ops) {
564   if (strictMode) {
565     strictModeFilteredOps.clear();
566     strictModeFilteredOps.insert(ops.begin(), ops.end());
567   }
568 
569   bool changed = false;
570   worklist.clear();
571   worklistMap.clear();
572   for (Operation *op : ops)
573     addToWorklist(op);
574 
575   // These are scratch vectors used in the folding loop below.
576   SmallVector<Value, 8> originalOperands, resultValues;
577   while (!worklist.empty()) {
578     Operation *op = popFromWorklist();
579 
580     // Nulls get added to the worklist when operations are removed, ignore
581     // them.
582     if (op == nullptr)
583       continue;
584 
585     // If the operation is trivially dead - remove it.
586     if (isOpTriviallyDead(op)) {
587       notifyOperationRemoved(op);
588       op->erase();
589       changed = true;
590       continue;
591     }
592 
593     // Collects all the operands and result uses of the given `op` into work
594     // list. Also remove `op` and nested ops from worklist.
595     originalOperands.assign(op->operand_begin(), op->operand_end());
596     auto preReplaceAction = [&](Operation *op) {
597       // Add the operands to the worklist for visitation.
598       addOperandsToWorklist(originalOperands);
599 
600       // Add all the users of the result to the worklist so we make sure
601       // to revisit them.
602       for (Value result : op->getResults())
603         for (Operation *userOp : result.getUsers()) {
604           if (!strictMode || strictModeFilteredOps.contains(userOp))
605             addToWorklist(userOp);
606         }
607       notifyOperationRemoved(op);
608     };
609 
610     // Add the given operation generated by the folder to the worklist.
611     auto processGeneratedConstants = [this](Operation *op) {
612       // Newly created ops are also simplified -- these are also "local".
613       addToWorklist(op);
614       // When strict mode is off, we don't need to maintain
615       // strictModeFilteredOps.
616       if (strictMode)
617         strictModeFilteredOps.insert(op);
618     };
619 
620     // Try to fold this op.
621     bool inPlaceUpdate;
622     if (succeeded(folder.tryToFold(op, processGeneratedConstants,
623                                    preReplaceAction, &inPlaceUpdate))) {
624       changed = true;
625       if (!inPlaceUpdate) {
626         // Op has been erased.
627         continue;
628       }
629     }
630 
631     // Try to match one of the patterns. The rewriter is automatically
632     // notified of any necessary changes, so there is nothing else to do
633     // here.
634     changed |= succeeded(matcher.matchAndRewrite(op, *this));
635   }
636 
637   return changed;
638 }
639 
640 /// Rewrites only `op` using the supplied canonicalization patterns and
641 /// folding. `erased` is set to true if the op is erased as a result of being
642 /// folded, replaced, or dead.
643 LogicalResult mlir::applyOpPatternsAndFold(
644     Operation *op, const FrozenRewritePatternSet &patterns, bool *erased) {
645   // Start the pattern driver.
646   GreedyRewriteConfig config;
647   OpPatternRewriteDriver driver(op->getContext(), patterns);
648   bool opErased;
649   LogicalResult converged =
650       driver.simplifyLocally(op, config.maxIterations, opErased);
651   if (erased)
652     *erased = opErased;
653   LLVM_DEBUG(if (failed(converged)) {
654     llvm::dbgs() << "The pattern rewrite doesn't converge after scanning "
655                  << config.maxIterations << " times";
656   });
657   return converged;
658 }
659 
660 bool mlir::applyOpPatternsAndFold(ArrayRef<Operation *> ops,
661                                   const FrozenRewritePatternSet &patterns,
662                                   bool strict) {
663   if (ops.empty())
664     return false;
665 
666   // Start the pattern driver.
667   MultiOpPatternRewriteDriver driver(ops.front()->getContext(), patterns,
668                                      strict);
669   return driver.simplifyLocally(ops);
670 }
671