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