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 ®ion : regions) 144 region.walk([this](Operation *op) { addToWorklist(op); }); 145 } else { 146 // Add all nested operations to the worklist in preorder. 147 for (auto ®ion : 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 ®ion) { 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