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       changed |= succeeded(matchResult);
259     }
260 
261     // After applying patterns, make sure that the CFG of each of the regions
262     // is kept up to date.
263     if (config.enableRegionSimplification)
264       changed |= succeeded(simplifyRegions(*this, regions));
265   } while (changed &&
266            (++iteration < config.maxIterations ||
267             config.maxIterations == GreedyRewriteConfig::kNoIterationLimit));
268 
269   // Whether the rewrite converges, i.e. wasn't changed in the last iteration.
270   return !changed;
271 }
272 
273 void GreedyPatternRewriteDriver::addToWorklist(Operation *op) {
274   // Check to see if the worklist already contains this op.
275   if (worklistMap.count(op))
276     return;
277 
278   worklistMap[op] = worklist.size();
279   worklist.push_back(op);
280 }
281 
282 Operation *GreedyPatternRewriteDriver::popFromWorklist() {
283   auto *op = worklist.back();
284   worklist.pop_back();
285 
286   // This operation is no longer in the worklist, keep worklistMap up to date.
287   if (op)
288     worklistMap.erase(op);
289   return op;
290 }
291 
292 void GreedyPatternRewriteDriver::removeFromWorklist(Operation *op) {
293   auto it = worklistMap.find(op);
294   if (it != worklistMap.end()) {
295     assert(worklist[it->second] == op && "malformed worklist data structure");
296     worklist[it->second] = nullptr;
297     worklistMap.erase(it);
298   }
299 }
300 
301 void GreedyPatternRewriteDriver::notifyOperationInserted(Operation *op) {
302   LLVM_DEBUG({
303     logger.startLine() << "** Insert  : '" << op->getName() << "'(" << op
304                        << ")\n";
305   });
306   addToWorklist(op);
307 }
308 
309 template <typename Operands>
310 void GreedyPatternRewriteDriver::addToWorklist(Operands &&operands) {
311   for (Value operand : operands) {
312     // If the use count of this operand is now < 2, we re-add the defining
313     // operation to the worklist.
314     // TODO: This is based on the fact that zero use operations
315     // may be deleted, and that single use values often have more
316     // canonicalization opportunities.
317     if (!operand || (!operand.use_empty() && !operand.hasOneUse()))
318       continue;
319     if (auto *defOp = operand.getDefiningOp())
320       addToWorklist(defOp);
321   }
322 }
323 
324 void GreedyPatternRewriteDriver::notifyOperationRemoved(Operation *op) {
325   addToWorklist(op->getOperands());
326   op->walk([this](Operation *operation) {
327     removeFromWorklist(operation);
328     folder.notifyRemoval(operation);
329   });
330 }
331 
332 void GreedyPatternRewriteDriver::notifyRootReplaced(Operation *op) {
333   LLVM_DEBUG({
334     logger.startLine() << "** Replace : '" << op->getName() << "'(" << op
335                        << ")\n";
336   });
337   for (auto result : op->getResults())
338     for (auto *user : result.getUsers())
339       addToWorklist(user);
340 }
341 
342 void GreedyPatternRewriteDriver::eraseOp(Operation *op) {
343   LLVM_DEBUG({
344     logger.startLine() << "** Erase   : '" << op->getName() << "'(" << op
345                        << ")\n";
346   });
347   PatternRewriter::eraseOp(op);
348 }
349 
350 LogicalResult GreedyPatternRewriteDriver::notifyMatchFailure(
351     Operation *op, function_ref<void(Diagnostic &)> reasonCallback) {
352   LLVM_DEBUG({
353     Diagnostic diag(op->getLoc(), DiagnosticSeverity::Remark);
354     reasonCallback(diag);
355     logger.startLine() << "** Failure : " << diag.str() << "\n";
356   });
357   return failure();
358 }
359 
360 /// Rewrite the regions of the specified operation, which must be isolated from
361 /// above, by repeatedly applying the highest benefit patterns in a greedy
362 /// work-list driven manner. Return success if no more patterns can be matched
363 /// in the result operation regions. Note: This does not apply patterns to the
364 /// top-level operation itself.
365 ///
366 LogicalResult
367 mlir::applyPatternsAndFoldGreedily(MutableArrayRef<Region> regions,
368                                    const FrozenRewritePatternSet &patterns,
369                                    GreedyRewriteConfig config) {
370   if (regions.empty())
371     return success();
372 
373   // The top-level operation must be known to be isolated from above to
374   // prevent performing canonicalizations on operations defined at or above
375   // the region containing 'op'.
376   auto regionIsIsolated = [](Region &region) {
377     return region.getParentOp()->hasTrait<OpTrait::IsIsolatedFromAbove>();
378   };
379   (void)regionIsIsolated;
380   assert(llvm::all_of(regions, regionIsIsolated) &&
381          "patterns can only be applied to operations IsolatedFromAbove");
382 
383   // Start the pattern driver.
384   GreedyPatternRewriteDriver driver(regions[0].getContext(), patterns, config);
385   bool converged = driver.simplify(regions);
386   LLVM_DEBUG(if (!converged) {
387     llvm::dbgs() << "The pattern rewrite doesn't converge after scanning "
388                  << config.maxIterations << " times\n";
389   });
390   return success(converged);
391 }
392 
393 //===----------------------------------------------------------------------===//
394 // OpPatternRewriteDriver
395 //===----------------------------------------------------------------------===//
396 
397 namespace {
398 /// This is a simple driver for the PatternMatcher to apply patterns and perform
399 /// folding on a single op. It repeatedly applies locally optimal patterns.
400 class OpPatternRewriteDriver : public PatternRewriter {
401 public:
402   explicit OpPatternRewriteDriver(MLIRContext *ctx,
403                                   const FrozenRewritePatternSet &patterns)
404       : PatternRewriter(ctx), matcher(patterns), folder(ctx) {
405     // Apply a simple cost model based solely on pattern benefit.
406     matcher.applyDefaultCostModel();
407   }
408 
409   LogicalResult simplifyLocally(Operation *op, int maxIterations, bool &erased);
410 
411   // These are hooks implemented for PatternRewriter.
412 protected:
413   /// If an operation is about to be removed, mark it so that we can let clients
414   /// know.
415   void notifyOperationRemoved(Operation *op) override {
416     opErasedViaPatternRewrites = true;
417   }
418 
419   // When a root is going to be replaced, its removal will be notified as well.
420   // So there is nothing to do here.
421   void notifyRootReplaced(Operation *op) override {}
422 
423 private:
424   /// The low-level pattern applicator.
425   PatternApplicator matcher;
426 
427   /// Non-pattern based folder for operations.
428   OperationFolder folder;
429 
430   /// Set to true if the operation has been erased via pattern rewrites.
431   bool opErasedViaPatternRewrites = false;
432 };
433 
434 } // namespace
435 
436 /// Performs the rewrites and folding only on `op`. The simplification
437 /// converges if the op is erased as a result of being folded, replaced, or
438 /// becoming dead, or no more changes happen in an iteration. Returns success if
439 /// the rewrite converges in `maxIterations`. `erased` is set to true if `op`
440 /// gets erased.
441 LogicalResult OpPatternRewriteDriver::simplifyLocally(Operation *op,
442                                                       int maxIterations,
443                                                       bool &erased) {
444   bool changed = false;
445   erased = false;
446   opErasedViaPatternRewrites = false;
447   int iterations = 0;
448   // Iterate until convergence or until maxIterations. Deletion of the op as
449   // a result of being dead or folded is convergence.
450   do {
451     changed = false;
452 
453     // If the operation is trivially dead - remove it.
454     if (isOpTriviallyDead(op)) {
455       op->erase();
456       erased = true;
457       return success();
458     }
459 
460     // Try to fold this op.
461     bool inPlaceUpdate;
462     if (succeeded(folder.tryToFold(op, /*processGeneratedConstants=*/nullptr,
463                                    /*preReplaceAction=*/nullptr,
464                                    &inPlaceUpdate))) {
465       changed = true;
466       if (!inPlaceUpdate) {
467         erased = true;
468         return success();
469       }
470     }
471 
472     // Try to match one of the patterns. The rewriter is automatically
473     // notified of any necessary changes, so there is nothing else to do here.
474     changed |= succeeded(matcher.matchAndRewrite(op, *this));
475     if ((erased = opErasedViaPatternRewrites))
476       return success();
477   } while (changed &&
478            (++iterations < maxIterations ||
479             maxIterations == GreedyRewriteConfig::kNoIterationLimit));
480 
481   // Whether the rewrite converges, i.e. wasn't changed in the last iteration.
482   return failure(changed);
483 }
484 
485 //===----------------------------------------------------------------------===//
486 // MultiOpPatternRewriteDriver
487 //===----------------------------------------------------------------------===//
488 
489 namespace {
490 
491 /// This is a specialized GreedyPatternRewriteDriver to apply patterns and
492 /// perform folding for a supplied set of ops. It repeatedly simplifies while
493 /// restricting the rewrites to only the provided set of ops or optionally
494 /// to those directly affected by it (result users or operand providers).
495 class MultiOpPatternRewriteDriver : public GreedyPatternRewriteDriver {
496 public:
497   explicit MultiOpPatternRewriteDriver(MLIRContext *ctx,
498                                        const FrozenRewritePatternSet &patterns,
499                                        bool strict)
500       : GreedyPatternRewriteDriver(ctx, patterns, GreedyRewriteConfig()),
501         strictMode(strict) {}
502 
503   bool simplifyLocally(ArrayRef<Operation *> op);
504 
505 private:
506   // Look over the provided operands for any defining operations that should
507   // be re-added to the worklist. This function should be called when an
508   // operation is modified or removed, as it may trigger further
509   // simplifications. If `strict` is set to true, only ops in
510   // `strictModeFilteredOps` are considered.
511   template <typename Operands>
512   void addOperandsToWorklist(Operands &&operands) {
513     for (Value operand : operands) {
514       if (auto *defOp = operand.getDefiningOp()) {
515         if (!strictMode || strictModeFilteredOps.contains(defOp))
516           addToWorklist(defOp);
517       }
518     }
519   }
520 
521   void notifyOperationRemoved(Operation *op) override {
522     GreedyPatternRewriteDriver::notifyOperationRemoved(op);
523     if (strictMode)
524       strictModeFilteredOps.erase(op);
525   }
526 
527   /// If `strictMode` is true, any pre-existing ops outside of
528   /// `strictModeFilteredOps` remain completely untouched by the rewrite driver.
529   /// If `strictMode` is false, operations that use results of (or supply
530   /// operands to) any rewritten ops stemming from the simplification of the
531   /// provided ops are in turn simplified; any other ops still remain untouched
532   /// (i.e., regardless of `strictMode`).
533   bool strictMode = false;
534 
535   /// The list of ops we are restricting our rewrites to if `strictMode` is on.
536   /// These include the supplied set of ops as well as new ops created while
537   /// rewriting those ops. This set is not maintained when strictMode is off.
538   llvm::SmallDenseSet<Operation *, 4> strictModeFilteredOps;
539 };
540 
541 } // namespace
542 
543 /// Performs the specified rewrites on `ops` while also trying to fold these ops
544 /// as well as any other ops that were in turn created due to these rewrite
545 /// patterns. Any pre-existing ops outside of `ops` remain completely
546 /// unmodified if `strictMode` is true. If `strictMode` is false, other
547 /// operations that use results of rewritten ops or supply operands to such ops
548 /// are in turn simplified; any other ops still remain unmodified (i.e.,
549 /// regardless of `strictMode`). Note that ops in `ops` could be erased as a
550 /// result of folding, becoming dead, or via pattern rewrites. Returns true if
551 /// at all any changes happened.
552 // Unlike `OpPatternRewriteDriver::simplifyLocally` which works on a single op
553 // or GreedyPatternRewriteDriver::simplify, this method just iterates until
554 // the worklist is empty. As our objective is to keep simplification "local",
555 // there is no strong rationale to re-add all operations into the worklist and
556 // rerun until an iteration changes nothing. If more widereaching simplification
557 // is desired, GreedyPatternRewriteDriver should be used.
558 bool MultiOpPatternRewriteDriver::simplifyLocally(ArrayRef<Operation *> ops) {
559   if (strictMode) {
560     strictModeFilteredOps.clear();
561     strictModeFilteredOps.insert(ops.begin(), ops.end());
562   }
563 
564   bool changed = false;
565   worklist.clear();
566   worklistMap.clear();
567   for (Operation *op : ops)
568     addToWorklist(op);
569 
570   // These are scratch vectors used in the folding loop below.
571   SmallVector<Value, 8> originalOperands, resultValues;
572   while (!worklist.empty()) {
573     Operation *op = popFromWorklist();
574 
575     // Nulls get added to the worklist when operations are removed, ignore
576     // them.
577     if (op == nullptr)
578       continue;
579 
580     // If the operation is trivially dead - remove it.
581     if (isOpTriviallyDead(op)) {
582       notifyOperationRemoved(op);
583       op->erase();
584       changed = true;
585       continue;
586     }
587 
588     // Collects all the operands and result uses of the given `op` into work
589     // list. Also remove `op` and nested ops from worklist.
590     originalOperands.assign(op->operand_begin(), op->operand_end());
591     auto preReplaceAction = [&](Operation *op) {
592       // Add the operands to the worklist for visitation.
593       addOperandsToWorklist(originalOperands);
594 
595       // Add all the users of the result to the worklist so we make sure
596       // to revisit them.
597       for (Value result : op->getResults())
598         for (Operation *userOp : result.getUsers()) {
599           if (!strictMode || strictModeFilteredOps.contains(userOp))
600             addToWorklist(userOp);
601         }
602       notifyOperationRemoved(op);
603     };
604 
605     // Add the given operation generated by the folder to the worklist.
606     auto processGeneratedConstants = [this](Operation *op) {
607       // Newly created ops are also simplified -- these are also "local".
608       addToWorklist(op);
609       // When strict mode is off, we don't need to maintain
610       // strictModeFilteredOps.
611       if (strictMode)
612         strictModeFilteredOps.insert(op);
613     };
614 
615     // Try to fold this op.
616     bool inPlaceUpdate;
617     if (succeeded(folder.tryToFold(op, processGeneratedConstants,
618                                    preReplaceAction, &inPlaceUpdate))) {
619       changed = true;
620       if (!inPlaceUpdate) {
621         // Op has been erased.
622         continue;
623       }
624     }
625 
626     // Try to match one of the patterns. The rewriter is automatically
627     // notified of any necessary changes, so there is nothing else to do
628     // here.
629     changed |= succeeded(matcher.matchAndRewrite(op, *this));
630   }
631 
632   return changed;
633 }
634 
635 /// Rewrites only `op` using the supplied canonicalization patterns and
636 /// folding. `erased` is set to true if the op is erased as a result of being
637 /// folded, replaced, or dead.
638 LogicalResult mlir::applyOpPatternsAndFold(
639     Operation *op, const FrozenRewritePatternSet &patterns, bool *erased) {
640   // Start the pattern driver.
641   GreedyRewriteConfig config;
642   OpPatternRewriteDriver driver(op->getContext(), patterns);
643   bool opErased;
644   LogicalResult converged =
645       driver.simplifyLocally(op, config.maxIterations, opErased);
646   if (erased)
647     *erased = opErased;
648   LLVM_DEBUG(if (failed(converged)) {
649     llvm::dbgs() << "The pattern rewrite doesn't converge after scanning "
650                  << config.maxIterations << " times";
651   });
652   return converged;
653 }
654 
655 bool mlir::applyOpPatternsAndFold(ArrayRef<Operation *> ops,
656                                   const FrozenRewritePatternSet &patterns,
657                                   bool strict) {
658   if (ops.empty())
659     return false;
660 
661   // Start the pattern driver.
662   MultiOpPatternRewriteDriver driver(ops.front()->getContext(), patterns,
663                                      strict);
664   return driver.simplifyLocally(ops);
665 }
666