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 ®ion : 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 ®ion : 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 ®ion) { 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