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