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