MLIR 24.0.0git
GreedyPatternRewriteDriver.cpp
Go to the documentation of this file.
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::applyPatternsGreedily.
10//
11//===----------------------------------------------------------------------===//
12
14
15#include "mlir/Config/mlir-config.h"
16#include "mlir/IR/Action.h"
17#include "mlir/IR/Dominance.h"
18#include "mlir/IR/Matchers.h"
19#include "mlir/IR/Operation.h"
21#include "mlir/IR/Verifier.h"
24#include "mlir/Transforms/CSE.h"
27#include "llvm/ADT/BitVector.h"
28#include "llvm/ADT/DenseMap.h"
29#include "llvm/ADT/ScopeExit.h"
30#include "llvm/ADT/SetVector.h"
31#include "llvm/ADT/SmallPtrSet.h"
32#include "llvm/Support/DebugLog.h"
33#include "llvm/Support/ScopedPrinter.h"
34#include "llvm/Support/raw_ostream.h"
35
36#ifdef MLIR_GREEDY_REWRITE_RANDOMIZER_SEED
37#include <random>
38#endif // MLIR_GREEDY_REWRITE_RANDOMIZER_SEED
39
40using namespace mlir;
41
42#define DEBUG_TYPE "greedy-rewriter"
43
44namespace {
45
46//===----------------------------------------------------------------------===//
47// Debugging Infrastructure
48//===----------------------------------------------------------------------===//
49
50#if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
51/// A helper struct that performs various "expensive checks" to detect broken
52/// rewrite patterns use the rewriter API incorrectly. A rewrite pattern is
53/// broken if:
54/// * IR does not verify after pattern application / folding.
55/// * Pattern returns "failure" but the IR has changed.
56/// * Pattern returns "success" but the IR has not changed.
57///
58/// This struct stores finger prints of ops to determine whether the IR has
59/// changed or not.
60struct ExpensiveChecks : public RewriterBase::ForwardingListener {
61 ExpensiveChecks(RewriterBase::Listener *listener, Operation *topLevel)
62 : RewriterBase::ForwardingListener(listener), topLevel(topLevel) {}
63
64 /// Compute finger prints of the given op and its nested ops.
65 void computeFingerPrints(Operation *topLevel) {
66 this->topLevel = topLevel;
67 this->topLevelFingerPrint.emplace(topLevel);
68 topLevel->walk([&](Operation *op) {
69 fingerprints.try_emplace(op, op, /*includeNested=*/false);
70 });
71 }
72
73 /// Clear all finger prints.
74 void clear() {
75 topLevel = nullptr;
76 topLevelFingerPrint.reset();
77 fingerprints.clear();
78 }
79
80 void notifyRewriteSuccess() {
81 if (!topLevel)
82 return;
83
84 // Make sure that the IR still verifies.
85 if (failed(verify(topLevel)))
86 llvm::report_fatal_error("IR failed to verify after pattern application");
87
88 // Pattern application success => IR must have changed.
89 OperationFingerPrint afterFingerPrint(topLevel);
90 if (*topLevelFingerPrint == afterFingerPrint) {
91 // Note: Run "mlir-opt -debug" to see which pattern is broken.
92 llvm::report_fatal_error(
93 "pattern returned success but IR did not change");
94 }
95 for (const auto &it : fingerprints) {
96 // Skip top-level op, its finger print is never invalidated.
97 if (it.first == topLevel)
98 continue;
99 // Note: Finger print computation may crash when an op was erased
100 // without notifying the rewriter. (Run with ASAN to see where the op was
101 // erased; the op was probably erased directly, bypassing the rewriter
102 // API.) Finger print computation does may not crash if a new op was
103 // created at the same memory location. (But then the finger print should
104 // have changed.)
105 if (it.second !=
106 OperationFingerPrint(it.first, /*includeNested=*/false)) {
107 // Note: Run "mlir-opt -debug" to see which pattern is broken.
108 llvm::report_fatal_error("operation finger print changed");
109 }
110 }
111 }
112
113 void notifyRewriteFailure() {
114 if (!topLevel)
115 return;
116
117 // Pattern application failure => IR must not have changed.
118 OperationFingerPrint afterFingerPrint(topLevel);
119 if (*topLevelFingerPrint != afterFingerPrint) {
120 // Note: Run "mlir-opt -debug" to see which pattern is broken.
121 llvm::report_fatal_error("pattern returned failure but IR did change");
122 }
123 }
124
125 void notifyFoldingSuccess() {
126 if (!topLevel)
127 return;
128
129 // Make sure that the IR still verifies.
130 if (failed(verify(topLevel)))
131 llvm::report_fatal_error("IR failed to verify after folding");
132 }
133
134protected:
135 /// Invalidate the finger print of the given op, i.e., remove it from the map.
136 void invalidateFingerPrint(Operation *op) { fingerprints.erase(op); }
137
138 void notifyBlockInserted(Block *block, Region *previous,
139 Region::iterator previousIt) override {
141 previousIt);
142
143 // Inserting or moving a block changes the region structure of both the
144 // new and previous owners. Either region may be detached.
145 invalidateFingerPrint(block->getParentOp());
146 if (previous)
147 invalidateFingerPrint(previous->getParentOp());
148 }
149
150 void notifyBlockErased(Block *block) override {
152
153 // The block structure (number of blocks, types of block arguments, etc.)
154 // is part of the fingerprint of the parent op.
155 // TODO: The parent op fingerprint should also be invalidated when modifying
156 // the block arguments of a block, but we do not have a
157 // `notifyBlockModified` callback yet.
158 invalidateFingerPrint(block->getParentOp());
159 }
160
162 OpBuilder::InsertPoint previous) override {
164 invalidateFingerPrint(op->getParentOp());
165 }
166
167 void notifyOperationModified(Operation *op) override {
169 invalidateFingerPrint(op);
170 }
171
172 void notifyOperationErased(Operation *op) override {
174 op->walk([this](Operation *op) { invalidateFingerPrint(op); });
175 }
176
177 /// Operation finger prints to detect invalid pattern API usage. IR is checked
178 /// against these finger prints after pattern application to detect cases
179 /// where IR was modified directly, bypassing the rewriter API.
181
182 /// Top-level operation of the current greedy rewrite.
183 Operation *topLevel = nullptr;
184
185 /// Finger print of the top-level operation.
186 std::optional<OperationFingerPrint> topLevelFingerPrint;
187};
188#endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
189
190#ifndef NDEBUG
191static Operation *getDumpRootOp(Operation *op) {
192 // Dump the parent op so that materialized constants are visible. If the op
193 // is a top-level op, dump it directly.
194 if (Operation *parentOp = op->getParentOp())
195 return parentOp;
196 return op;
197}
198static void logSuccessfulFolding(Operation *op) {
199 LDBG() << "// *** IR Dump After Successful Folding ***\n"
200 << OpWithFlags(op, OpPrintingFlags().elideLargeElementsAttrs());
201}
202#endif // NDEBUG
203
204//===----------------------------------------------------------------------===//
205// Worklist
206//===----------------------------------------------------------------------===//
207
208/// A LIFO worklist of operations with efficient removal and set semantics.
209///
210/// This class maintains a vector of operations and a mapping of operations to
211/// positions in the vector, so that operations can be removed efficiently at
212/// random. When an operation is removed, it is replaced with nullptr. Such
213/// nullptr are skipped when pop'ing elements.
214class Worklist {
215public:
216 Worklist();
217
218 /// Clear the worklist.
219 void clear();
220
221 /// Return whether the worklist is empty.
222 bool empty() const;
223
224 /// Push an operation to the end of the worklist, unless the operation is
225 /// already on the worklist.
226 void push(Operation *op);
227
228 /// Pop the an operation from the end of the worklist. Only allowed on
229 /// non-empty worklists.
230 Operation *pop();
231
232 /// Remove an operation from the worklist.
233 void remove(Operation *op);
234
235 /// Reverse the worklist.
236 void reverse();
237
238protected:
239 /// The worklist of operations.
240 std::vector<Operation *> list;
241
242 /// A mapping of operations to positions in `list`.
244};
245
246Worklist::Worklist() { list.reserve(64); }
247
248void Worklist::clear() {
249 list.clear();
250 map.clear();
251}
252
253bool Worklist::empty() const {
254 // Skip all nullptr.
255 return !llvm::any_of(list,
256 [](Operation *op) { return static_cast<bool>(op); });
257}
258
259void Worklist::push(Operation *op) {
260 assert(op && "cannot push nullptr to worklist");
261 // Check to see if the worklist already contains this op.
262 if (!map.insert({op, list.size()}).second)
263 return;
264 list.push_back(op);
265}
266
267Operation *Worklist::pop() {
268 assert(!empty() && "cannot pop from empty worklist");
269 // Skip and remove all trailing nullptr.
270 while (!list.back())
271 list.pop_back();
272 Operation *op = list.back();
273 list.pop_back();
274 map.erase(op);
275 // Cleanup: Remove all trailing nullptr.
276 while (!list.empty() && !list.back())
277 list.pop_back();
278 return op;
279}
280
281void Worklist::remove(Operation *op) {
282 assert(op && "cannot remove nullptr from worklist");
283 auto it = map.find(op);
284 if (it != map.end()) {
285 assert(list[it->second] == op && "malformed worklist data structure");
286 list[it->second] = nullptr;
287 map.erase(it);
288 }
289}
290
291void Worklist::reverse() {
292 std::reverse(list.begin(), list.end());
293 for (size_t i = 0, e = list.size(); i != e; ++i)
294 map[list[i]] = i;
295}
296
297#ifdef MLIR_GREEDY_REWRITE_RANDOMIZER_SEED
298/// A worklist that pops elements at a random position. This worklist is for
299/// testing/debugging purposes only. It can be used to ensure that lowering
300/// pipelines work correctly regardless of the order in which ops are processed
301/// by the GreedyPatternRewriteDriver.
302class RandomizedWorklist : public Worklist {
303public:
304 RandomizedWorklist() : Worklist() {
305 generator.seed(MLIR_GREEDY_REWRITE_RANDOMIZER_SEED);
306 }
307
308 /// Pop a random non-empty op from the worklist.
309 Operation *pop() {
310 Operation *op = nullptr;
311 do {
312 assert(!list.empty() && "cannot pop from empty worklist");
313 int64_t pos = generator() % list.size();
314 op = list[pos];
315 list.erase(list.begin() + pos);
316 for (int64_t i = pos, e = list.size(); i < e; ++i)
317 map[list[i]] = i;
318 map.erase(op);
319 } while (!op);
320 return op;
321 }
322
323private:
324 std::minstd_rand0 generator;
325};
326#endif // MLIR_GREEDY_REWRITE_RANDOMIZER_SEED
327
328//===----------------------------------------------------------------------===//
329// GreedyPatternRewriteDriver
330//===----------------------------------------------------------------------===//
331
332/// Tracks which blocks are reachable from their region's entry block while
333/// patterns rewrite the CFG, so that the driver can skip operations in blocks
334/// that a rewrite disconnected. Notifications only record what changed; the
335/// queries, which the driver issues between rewrites, bring the cache up to
336/// date. `Block::isReachable` is not used because it traverses the region on
337/// every call, while the driver queries once per processed operation. All
338/// notifications are forwarded to the wrapped listener.
339class ReachabilityListener : public RewriterBase::ForwardingListener {
340public:
341 ReachabilityListener(RewriterBase::Listener *listener, Region *scope)
342 : RewriterBase::ForwardingListener(listener), scope(scope) {}
343
344 /// Return whether `op` is reachable: its block is reachable from the entry
345 /// block of its region, and so is every containing block up to `scope`.
346 /// Must be called between rewrites, when the IR is in a consistent state.
347 bool isReachable(Operation *op);
348
349 /// Drop all cached reachability information.
350 void clear() { regionStates.clear(); }
351
352 void notifyBlockInserted(Block *block, Region *previous,
353 Region::iterator previousIt) override;
354 void notifyBlockErased(Block *block) override;
355 void notifyOperationInserted(Operation *op,
356 OpBuilder::InsertPoint previous) override;
357 void notifyOperationModified(Operation *op) override;
358 void notifyOperationErased(Operation *op) override;
359
360private:
361 /// The reachability cache of one region.
362 struct RegionState {
363 /// The entry block at the time of the traversal. The traversal starts
364 /// from the entry block, so a different entry block invalidates the cache.
365 Block *entry = nullptr;
366 /// The reachable blocks, mapped to their successors within the region as
367 /// of the last query. The snapshots let the next query tell lost edges
368 /// from added ones.
370 /// Blocks of `reachable` whose reachability could not be decided
371 /// incrementally. A query for one of them traverses the region. Marking
372 /// and traversing both cost O(blocks), so a lost edge into a loop whose
373 /// target keeps other predecessors costs O(blocks) per rewrite if one of
374 /// the marked blocks is queried before the next change. Bottom-up
375 /// processing has usually popped the operations of these blocks already;
376 /// top-down processing pops them next.
377 llvm::SmallDenseSet<Block *, 4> uncertain;
378 /// Blocks whose terminator changed since the last query, in notification
379 /// order, so that the cache evolves deterministically.
380 llvm::SmallSetVector<Block *, 4> changedBlocks;
381 /// Blocks erased from or moved out of the region since the last query.
382 /// Their pointers are only compared, never dereferenced.
383 llvm::SmallDenseSet<Block *, 4> removedBlocks;
384
385 /// Rebuild the cache from the entry block of `region`.
386 void traverse(Region *region);
387 /// Add `roots` and every block reachable from them to `reachable`. The
388 /// added blocks are uncertain if `isUncertain` is set.
389 void addReachable(SmallVector<Block *> roots, bool isUncertain);
390 /// Mark `block` and every block behind it in the snapshots uncertain.
391 void markUncertain(Block *block);
392 /// Apply the pending changes, if any. On failure, the state is
393 /// inconsistent and the region must be traversed again.
394 LogicalResult applyChanges();
395 };
396
397 /// Record that the terminator of `block` changed. Only tracked regions
398 /// record changes, so no pointer to a block of an untracked region is kept.
399 void markChanged(Block *block) {
400 if (!block)
401 return;
402 auto it = regionStates.find(block->getParent());
403 if (it != regionStates.end())
404 it->second.changedBlocks.insert(block);
405 }
406 /// Record that `block` left `region`.
407 void markRemoved(Region *region, Block *block) {
408 auto it = regionStates.find(region);
409 if (it != regionStates.end())
410 it->second.removedBlocks.insert(block);
411 }
412 bool isBlockReachable(Block *block);
413
414 /// The tracked regions. A region without an entry is traversed by the next
415 /// query that needs it.
417
418 Region *const scope;
419};
420
421/// This is a worklist-driven driver for the PatternMatcher, which repeatedly
422/// applies the locally optimal patterns.
423///
424/// This abstract class manages the worklist and contains helper methods for
425/// rewriting ops on the worklist. Derived classes specify how ops are added
426/// to the worklist in the beginning.
427class GreedyPatternRewriteDriver : public RewriterBase::Listener {
428protected:
429 explicit GreedyPatternRewriteDriver(MLIRContext *ctx,
430 const FrozenRewritePatternSet &patterns,
431 const GreedyRewriteConfig &config);
432
433 /// Add the given operation to the worklist.
434 void addSingleOpToWorklist(Operation *op);
435
436 /// Add the given operation and its ancestors to the worklist.
437 void addToWorklist(Operation *op);
438
439 /// Notify the driver that the specified operation may have been modified
440 /// in-place. The operation is added to the worklist.
441 void notifyOperationModified(Operation *op) override;
442
443 /// Notify the driver that the specified operation was inserted. Update the
444 /// worklist as needed: The operation is enqueued depending on scope and
445 /// strict mode.
446 void notifyOperationInserted(Operation *op,
447 OpBuilder::InsertPoint previous) override;
448
449 /// Notify the driver that the specified operation was removed. Update the
450 /// worklist as needed: The operation and its children are removed from the
451 /// worklist.
452 void notifyOperationErased(Operation *op) override;
453
454 /// Notify the driver that the specified operation was replaced. Update the
455 /// worklist as needed: New users are added enqueued.
456 void notifyOperationReplaced(Operation *op, ValueRange replacement) override;
457
458 /// Process ops until the worklist is empty or `config.maxNumRewrites` is
459 /// reached. Skip operations in unreachable blocks. Return `true` if any IR
460 /// was changed.
461 bool processWorklist();
462
463 /// The pattern rewriter that is used for making IR modifications and is
464 /// passed to rewrite patterns.
465 PatternRewriter rewriter;
466
467 /// The worklist for this transformation keeps track of the operations that
468 /// need to be (re)visited.
469#ifdef MLIR_GREEDY_REWRITE_RANDOMIZER_SEED
470 RandomizedWorklist worklist;
471#else
472 Worklist worklist;
473#endif // MLIR_GREEDY_REWRITE_RANDOMIZER_SEED
474
475 /// Configuration information for how to simplify.
476 const GreedyRewriteConfig config;
477
478 /// The list of ops we are restricting our rewrites to. These include the
479 /// supplied set of ops as well as new ops created while rewriting those ops
480 /// depending on `strictMode`. This set is not maintained when
481 /// `config.strictMode` is GreedyRewriteStrictness::AnyOp.
482 llvm::SmallDenseSet<Operation *, 4> strictModeFilteredOps;
483
484 /// Tracks block reachability while the worklist is processed. The rewriter
485 /// notifies this listener, which forwards to the driver.
486 ReachabilityListener reachabilityListener;
487
488private:
489 /// Look over the provided operands for any defining operations that should
490 /// be re-added to the worklist. This function should be called when an
491 /// operation is modified or removed, as it may trigger further
492 /// simplifications.
493 void addOperandsToWorklist(Operation *op);
494
495 /// Notify the driver that the given block was inserted.
496 void notifyBlockInserted(Block *block, Region *previous,
497 Region::iterator previousIt) override;
498
499 /// Notify the driver that the given block is about to be removed.
500 void notifyBlockErased(Block *block) override;
501
502 /// For debugging only: Notify the driver of a pattern match failure.
503 void
504 notifyMatchFailure(Location loc,
505 function_ref<void(Diagnostic &)> reasonCallback) override;
506
507#ifndef NDEBUG
508 /// A raw output stream used to prefix the debug log.
509
510 llvm::impl::raw_ldbg_ostream os{(Twine("[") + DEBUG_TYPE + ":1] ").str(),
511 llvm::dbgs()};
512 /// A logger used to emit information during the application process.
513 llvm::ScopedPrinter logger{os};
514#endif
515
516 /// The low-level pattern applicator.
517 PatternApplicator matcher;
518
519#if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
520 ExpensiveChecks expensiveChecks;
521#endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
522};
523} // namespace
524
525/// Return whether `op` may define successors of its block.
528 op->getNumSuccessors() != 0;
529}
530
531/// Collect the distinct successors of `block` that are in the same region.
532/// A successor in another region is invalid IR that cannot make
533/// a block of this region reachable.
535 SmallPtrSet<Block *, 2> successors;
536 for (Block *successor : block->getSuccessors())
537 if (successor->getParent() == block->getParent())
538 successors.insert(successor);
539 return successors;
540}
541
542void ReachabilityListener::RegionState::traverse(Region *region) {
543 // Clear rather than reassign, to keep the allocations for the next
544 // traversal.
545 reachable.clear();
546 uncertain.clear();
547 changedBlocks.clear();
548 removedBlocks.clear();
549 entry = &region->front();
550 addReachable({entry}, /*isUncertain=*/false);
551}
552
553void ReachabilityListener::RegionState::addReachable(SmallVector<Block *> roots,
554 bool isUncertain) {
555 while (!roots.empty()) {
556 auto [it, inserted] = reachable.try_emplace(roots.pop_back_val());
557 if (!inserted)
558 continue;
559 auto successors = getRegionSuccessors(it->first);
560 it->second.append(successors.begin(), successors.end());
561 if (isUncertain)
562 uncertain.insert(it->first);
563 roots.append(it->second);
564 }
565}
566
567void ReachabilityListener::RegionState::markUncertain(Block *block) {
568 SmallVector<Block *> worklist{block};
569 while (!worklist.empty()) {
570 Block *current = worklist.pop_back_val();
571 auto it = reachable.find(current);
572 if (it == reachable.end() || !uncertain.insert(current).second)
573 continue;
574 worklist.append(it->second);
575 }
576}
577
578LogicalResult ReachabilityListener::RegionState::applyChanges() {
579 if (changedBlocks.empty() && removedBlocks.empty())
580 return success();
581 // Blocks are dropped after the loop, so that the snapshot of a lost block
582 // remains available to every changed block that lost an edge to it.
583 SmallVector<Block *> unreachable;
584 for (Block *changed : changedBlocks) {
585 // A removed block is no longer part of the region, and a change in an
586 // uncached block cannot make any block reachable.
587 if (removedBlocks.contains(changed))
588 continue;
589 auto it = reachable.find(changed);
590 if (it == reachable.end())
591 continue;
592 // Keep even a disconnected block's snapshot up to date: a later rewrite
593 // may reconnect it without querying the block itself. Its added successors
594 // must remain uncertain until a traversal establishes their reachability.
595 if (changed != entry && changed->hasNoPredecessors())
596 markUncertain(changed);
597 // Keep the snapshot available while marking lost successors uncertain.
598 // A lost self-edge can make that traversal visit `changed` itself.
599 const auto &old = it->second;
600 auto current = getRegionSuccessors(changed);
601 for (Block *lost : old) {
602 if (current.contains(lost))
603 continue;
604 // The successors of a reachable block are reachable, so `lost` has a
605 // snapshot unless the cache is inconsistent. This is not expected to
606 // happen; the failure is a safety net that forces a traversal.
607 auto lostIt = reachable.find(lost);
608 if (lostIt == reachable.end())
609 return failure();
610 // `lost` and every block behind it may have become unreachable. When
611 // `lost` is gone or has no predecessors left, it is unreachable; if all
612 // of its cached successors are still successors of `changed`, nothing
613 // else changed. This is the shape left by folding a constant conditional
614 // branch and by merging a block into its predecessor. Any other lost
615 // edge leaves the blocks behind `lost` uncertain until a query for one
616 // of them traverses the region.
617 bool isGone = removedBlocks.contains(lost) || lost->hasNoPredecessors();
618 if (isGone && llvm::all_of(lostIt->second, [&](Block *successor) {
619 return current.contains(successor);
620 })) {
621 unreachable.push_back(lost);
622 continue;
623 }
624 markUncertain(lost);
625 }
626 // Added edges can only make more blocks reachable. Blocks reached from an
627 // uncertain block are uncertain as well.
628 SmallVector<Block *> roots;
629 for (Block *successor : current)
630 if (!reachable.contains(successor))
631 roots.push_back(successor);
632 // Nothing was inserted into `reachable` since `it` was looked up.
633 it->second.assign(current.begin(), current.end());
634 addReachable(std::move(roots), uncertain.contains(changed));
635 }
636 // Drop every block that left the region, so that no stale key can match a
637 // block inserted later at the same address.
638 unreachable.append(removedBlocks.begin(), removedBlocks.end());
639 for (Block *block : unreachable) {
640 reachable.erase(block);
641 uncertain.erase(block);
642 }
643 changedBlocks.clear();
644 removedBlocks.clear();
645 return success();
646}
647
648bool ReachabilityListener::isBlockReachable(Block *block) {
649 Region *region = block->getParent();
650 // A detached block is treated as reachable, as is the entry block.
651 if (!region || block == &region->front())
652 return true;
653 if (block->hasNoPredecessors())
654 return false;
655 auto [it, inserted] = regionStates.try_emplace(region);
656 RegionState &state = it->second;
657 bool isStale = inserted || state.entry != &region->front();
658 if (isStale || failed(state.applyChanges()) ||
659 state.uncertain.contains(block))
660 state.traverse(region);
661 bool result = state.reachable.contains(block);
662#if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
663 assert(result == region->front().isReachable(block) &&
664 "reachability cache disagrees with a traversal");
665#endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
666 return result;
667}
668
669bool ReachabilityListener::isReachable(Operation *op) {
670 // A nested region can be locally reachable while its owning operation is in
671 // an unreachable block, so check every containing block up to the scope.
672 while (Block *block = op->getBlock()) {
673 if (!isBlockReachable(block))
674 return false;
675 Region *region = block->getParent();
676 if (!region || region == scope)
677 break;
678 op = region->getParentOp();
679 }
680 return true;
681}
682
683void ReachabilityListener::notifyBlockInserted(Block *block, Region *previous,
684 Region::iterator previousIt) {
685 Region *region = block->getParent();
686 // A block moved within its region keeps its reachability, unless it becomes
687 // the entry block, which the next query detects. A block that is new to the
688 // region is unreachable until a recorded terminator change adds an edge to
689 // it, but a stale entry from an earlier removal cannot be trusted.
690 if (previous != region) {
691 if (previous)
692 markRemoved(previous, block);
693 auto it = regionStates.find(region);
694 if (it != regionStates.end() && (it->second.removedBlocks.contains(block) ||
695 it->second.reachable.contains(block)))
696 regionStates.erase(it);
697 }
699 previousIt);
700}
701
702void ReachabilityListener::notifyBlockErased(Block *block) {
703 markRemoved(block->getParent(), block);
705}
706
707void ReachabilityListener::notifyOperationInserted(
708 Operation *op, OpBuilder::InsertPoint previous) {
709 if (mayDefineSuccessors(op)) {
710 markChanged(op->getBlock());
711 if (previous.isSet())
712 markChanged(previous.getBlock());
713 }
715}
716
717void ReachabilityListener::notifyOperationModified(Operation *op) {
718 if (mayDefineSuccessors(op))
719 markChanged(op->getBlock());
721}
722
723void ReachabilityListener::notifyOperationErased(Operation *op) {
724 if (mayDefineSuccessors(op))
725 markChanged(op->getBlock());
726 // Do not retain keys for regions that are about to be destroyed.
727 for (Region &region : op->getRegions())
728 regionStates.erase(&region);
730}
731
732GreedyPatternRewriteDriver::GreedyPatternRewriteDriver(
733 MLIRContext *ctx, const FrozenRewritePatternSet &patterns,
734 const GreedyRewriteConfig &config)
735 : rewriter(ctx), config(config),
736 reachabilityListener(this, config.getScope()), matcher(patterns)
737#if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
738 // clang-format off
739 , expensiveChecks(
740 /*listener=*/&reachabilityListener,
741 /*topLevel=*/config.getScope() ? config.getScope()->getParentOp()
742 : nullptr)
743// clang-format on
744#endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
745{
746 // Apply a simple cost model based solely on pattern benefit.
747 matcher.applyDefaultCostModel();
748
749 // Set up listener.
750#if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
751 // Chain notifications through the debug handler, reachability listener, and
752 // worklist driver, in that order.
753 rewriter.setListener(&expensiveChecks);
754#else
755 rewriter.setListener(&reachabilityListener);
756#endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
757}
758
759bool GreedyPatternRewriteDriver::processWorklist() {
760 // The cache is only maintained while this worklist pass runs. Between
761 // passes, the IR changes without notifications, e.g. in region
762 // simplification and in the unreachable-block sweep.
763 llvm::scope_exit clearReachability([&] { reachabilityListener.clear(); });
764
765#ifndef NDEBUG
766 const char *logLineComment =
767 "//===-------------------------------------------===//\n";
768
769 /// A utility function to log a process result for the given reason.
770 auto logResult = [&](StringRef result, const llvm::Twine &msg = {}) {
771 logger.unindent();
772 logger.startLine() << "} -> " << result;
773 if (!msg.isTriviallyEmpty())
774 logger.getOStream() << " : " << msg;
775 logger.getOStream() << "\n";
776 };
777 auto logResultWithLine = [&](StringRef result, const llvm::Twine &msg = {}) {
778 logResult(result, msg);
779 logger.startLine() << logLineComment;
780 };
781#endif
782
783 bool changed = false;
784 int64_t numRewrites = 0;
785 while (!worklist.empty() &&
786 (numRewrites < config.getMaxNumRewrites() ||
788 auto *op = worklist.pop();
789 if (!reachabilityListener.isReachable(op)) {
790 LLVM_DEBUG(logger.startLine() << "Skipping unreachable operation : '"
791 << op->getName() << "'(" << op << ")\n");
792 continue;
793 }
794
795 LLVM_DEBUG({
796 logger.getOStream() << "\n";
797 logger.startLine() << logLineComment;
798 logger.startLine() << "Processing operation : '" << op->getName() << "'("
799 << op << ") {\n";
800 logger.indent();
801
802 // If the operation has no regions, just print it here.
803 if (op->getNumRegions() == 0) {
804 op->print(
805 logger.startLine(),
806 OpPrintingFlags().printGenericOpForm().elideLargeElementsAttrs());
807 logger.getOStream() << "\n\n";
808 }
809 });
810
811 // If the operation is trivially dead - remove it.
812 if (isOpTriviallyDead(op)) {
813 rewriter.eraseOp(op);
814 changed = true;
815
816 LLVM_DEBUG(logResultWithLine("success", "operation is trivially dead"));
817 continue;
818 }
819
820 // Try to fold this op. Do not fold constant ops. That would lead to an
821 // infinite folding loop, as every constant op would be folded to an
822 // Attribute and then immediately be rematerialized as a constant op, which
823 // is then put on the worklist.
824 if (config.isFoldingEnabled() && !op->hasTrait<OpTrait::ConstantLike>()) {
825 SmallVector<OpFoldResult> foldResults;
826 if (succeeded(op->fold(foldResults))) {
827 LLVM_DEBUG(logResultWithLine("success", "operation was folded"));
828#ifndef NDEBUG
829 Operation *dumpRootOp = getDumpRootOp(op);
830#endif // NDEBUG
831 if (foldResults.empty()) {
832 // Folding modified the op in place without going through the
833 // rewriter.
834 // Notify both the reachability listener and the worklist driver.
835 reachabilityListener.notifyOperationModified(op);
836 changed = true;
837 LLVM_DEBUG(logSuccessfulFolding(dumpRootOp));
838#if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
839 expensiveChecks.notifyFoldingSuccess();
840#endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
841 continue;
842 }
843
844 // Op results can be replaced with `foldResults`.
845 assert(foldResults.size() == op->getNumResults() &&
846 "folder produced incorrect number of results");
847 OpBuilder::InsertionGuard g(rewriter);
848 rewriter.setInsertionPoint(op);
849 SmallVector<Value> replacements;
850 bool materializationSucceeded = true;
851 for (auto [ofr, resultType] :
852 llvm::zip_equal(foldResults, op->getResultTypes())) {
853 if (auto value = dyn_cast<Value>(ofr)) {
854 assert(value.getType() == resultType &&
855 "folder produced value of incorrect type");
856 replacements.push_back(value);
857 continue;
858 }
859 // Materialize Attributes as SSA values.
860 Operation *constOp = op->getDialect()->materializeConstant(
861 rewriter, cast<Attribute>(ofr), resultType, op->getLoc());
862
863 if (!constOp) {
864 // If materialization fails, cleanup any operations generated for
865 // the previous results.
866 llvm::SmallDenseSet<Operation *> replacementOps;
867 for (Value replacement : replacements) {
868 assert(replacement.use_empty() &&
869 "folder reused existing op for one result but constant "
870 "materialization failed for another result");
871 replacementOps.insert(replacement.getDefiningOp());
872 }
873 for (Operation *op : replacementOps) {
874 rewriter.eraseOp(op);
875 }
876
877 materializationSucceeded = false;
878 break;
879 }
880
881 assert(constOp->hasTrait<OpTrait::ConstantLike>() &&
882 "materializeConstant produced op that is not a ConstantLike");
883 assert(constOp->getResultTypes()[0] == resultType &&
884 "materializeConstant produced incorrect result type");
885 replacements.push_back(constOp->getResult(0));
886 }
887
888 if (materializationSucceeded) {
889 rewriter.replaceOp(op, replacements);
890 changed = true;
891 LLVM_DEBUG(logSuccessfulFolding(dumpRootOp));
892#if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
893 expensiveChecks.notifyFoldingSuccess();
894#endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
895 continue;
896 }
897 }
898 }
899
900 // Try to match one of the patterns. The rewriter is automatically
901 // notified of any necessary changes, so there is nothing else to do
902 // here.
903 auto canApplyCallback = [&](const Pattern &pattern) {
904 LLVM_DEBUG({
905 logger.getOStream() << "\n";
906 logger.startLine() << "* Pattern " << pattern.getDebugName() << " : '"
907 << op->getName() << " -> (";
908 llvm::interleaveComma(pattern.getGeneratedOps(), logger.getOStream());
909 logger.getOStream() << ")' {\n";
910 logger.indent();
911 });
912 if (RewriterBase::Listener *listener = config.getListener())
913 listener->notifyPatternBegin(pattern, op);
914 return true;
915 };
916 function_ref<bool(const Pattern &)> canApply = canApplyCallback;
917 auto onFailureCallback = [&](const Pattern &pattern) {
918 LLVM_DEBUG(logResult("failure", "pattern failed to match"));
919 if (RewriterBase::Listener *listener = config.getListener())
920 listener->notifyPatternEnd(pattern, failure());
921 };
922 function_ref<void(const Pattern &)> onFailure = onFailureCallback;
923 auto onSuccessCallback = [&](const Pattern &pattern) {
924 LLVM_DEBUG(logResult("success", "pattern applied successfully"));
925 if (RewriterBase::Listener *listener = config.getListener())
926 listener->notifyPatternEnd(pattern, success());
927 return success();
928 };
929 function_ref<LogicalResult(const Pattern &)> onSuccess = onSuccessCallback;
930
931#ifdef NDEBUG
932 // Optimization: PatternApplicator callbacks are not needed when running in
933 // optimized mode and without a listener.
934 if (!config.getListener()) {
935 canApply = nullptr;
936 onFailure = nullptr;
937 onSuccess = nullptr;
938 }
939#endif // NDEBUG
940
941#if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
942 if (config.getScope()) {
943 expensiveChecks.computeFingerPrints(config.getScope()->getParentOp());
944 }
945 llvm::scope_exit clearFingerprints([&]() { expensiveChecks.clear(); });
946#endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
947
948 LogicalResult matchResult =
949 matcher.matchAndRewrite(op, rewriter, canApply, onFailure, onSuccess);
950
951 if (succeeded(matchResult)) {
952 LLVM_DEBUG(logResultWithLine("success", "at least one pattern matched"));
953#if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
954 expensiveChecks.notifyRewriteSuccess();
955#endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
956 changed = true;
957 ++numRewrites;
958 } else {
959 LLVM_DEBUG(logResultWithLine("failure", "all patterns failed to match"));
960#if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
961 expensiveChecks.notifyRewriteFailure();
962#endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
963 }
964 }
965
966 return changed;
967}
968
969void GreedyPatternRewriteDriver::addToWorklist(Operation *op) {
970 assert(op && "expected valid op");
971 // Gather potential ancestors while looking for a "scope" parent region.
972 SmallVector<Operation *, 8> ancestors;
973 Region *region = nullptr;
974 do {
975 ancestors.push_back(op);
976 region = op->getParentRegion();
977 if (config.getScope() == region) {
978 // Scope (can be `nullptr`) was reached. Stop traveral and enqueue ops.
979 for (Operation *op : ancestors)
980 addSingleOpToWorklist(op);
981 return;
982 }
983 if (region == nullptr)
984 return;
985 } while ((op = region->getParentOp()));
986}
987
988void GreedyPatternRewriteDriver::addSingleOpToWorklist(Operation *op) {
989 if (config.getStrictness() == GreedyRewriteStrictness::AnyOp ||
990 strictModeFilteredOps.contains(op))
991 worklist.push(op);
992}
993
994void GreedyPatternRewriteDriver::notifyBlockInserted(
995 Block *block, Region *previous, Region::iterator previousIt) {
996 if (RewriterBase::Listener *listener = config.getListener())
997 listener->notifyBlockInserted(block, previous, previousIt);
998}
999
1000void GreedyPatternRewriteDriver::notifyBlockErased(Block *block) {
1001 if (RewriterBase::Listener *listener = config.getListener())
1002 listener->notifyBlockErased(block);
1003}
1004
1005void GreedyPatternRewriteDriver::notifyOperationInserted(
1006 Operation *op, OpBuilder::InsertPoint previous) {
1007 LLVM_DEBUG({
1008 logger.startLine() << "** Insert : '" << op->getName() << "'(" << op
1009 << ")\n";
1010 });
1011 if (RewriterBase::Listener *listener = config.getListener())
1012 listener->notifyOperationInserted(op, previous);
1013 if (config.getStrictness() == GreedyRewriteStrictness::ExistingAndNewOps)
1014 strictModeFilteredOps.insert(op);
1015 addToWorklist(op);
1016}
1017
1018void GreedyPatternRewriteDriver::notifyOperationModified(Operation *op) {
1019 LLVM_DEBUG({
1020 logger.startLine() << "** Modified: '" << op->getName() << "'(" << op
1021 << ")\n";
1022 });
1023 if (RewriterBase::Listener *listener = config.getListener())
1024 listener->notifyOperationModified(op);
1025 addToWorklist(op);
1026}
1027
1028void GreedyPatternRewriteDriver::addOperandsToWorklist(Operation *op) {
1029 for (Value operand : op->getOperands()) {
1030 // If this operand currently has at most 2 users, add its defining op to the
1031 // worklist. Indeed, after the op is deleted, then the operand will have at
1032 // most 1 user left. If it has 0 users left, it can be deleted too,
1033 // and if it has 1 user left, there may be further canonicalization
1034 // opportunities.
1035 if (!operand)
1036 continue;
1037
1038 auto *defOp = operand.getDefiningOp();
1039 if (!defOp)
1040 continue;
1041
1042 Operation *otherUser = nullptr;
1043 bool hasMoreThanTwoUses = false;
1044 for (auto *user : operand.getUsers()) {
1045 if (user == op || user == otherUser)
1046 continue;
1047 if (!otherUser) {
1048 otherUser = user;
1049 continue;
1050 }
1051 hasMoreThanTwoUses = true;
1052 break;
1053 }
1054 if (hasMoreThanTwoUses)
1055 continue;
1056
1057 addToWorklist(defOp);
1058 }
1059}
1060
1061void GreedyPatternRewriteDriver::notifyOperationErased(Operation *op) {
1062 LLVM_DEBUG({
1063 logger.startLine() << "** Erase : '" << op->getName() << "'(" << op
1064 << ")\n";
1065 });
1066
1067#ifndef NDEBUG
1068 // Only ops that are within the configured scope are added to the worklist of
1069 // the greedy pattern rewriter. Moreover, the parent op of the scope region is
1070 // the part of the IR that is taken into account for the "expensive checks".
1071 // A greedy pattern rewrite is not allowed to erase the parent op of the scope
1072 // region, as that would break the worklist handling and the expensive checks.
1073 if (Region *scope = config.getScope(); scope->getParentOp() == op)
1074 llvm_unreachable(
1075 "scope region must not be erased during greedy pattern rewrite");
1076#endif // NDEBUG
1077
1078 if (RewriterBase::Listener *listener = config.getListener())
1079 listener->notifyOperationErased(op);
1080
1081 addOperandsToWorklist(op);
1082 worklist.remove(op);
1083
1084 if (config.getStrictness() != GreedyRewriteStrictness::AnyOp)
1085 strictModeFilteredOps.erase(op);
1086}
1087
1088void GreedyPatternRewriteDriver::notifyOperationReplaced(
1089 Operation *op, ValueRange replacement) {
1090 LLVM_DEBUG({
1091 logger.startLine() << "** Replace : '" << op->getName() << "'(" << op
1092 << ")\n";
1093 });
1094 if (RewriterBase::Listener *listener = config.getListener())
1095 listener->notifyOperationReplaced(op, replacement);
1096}
1097
1098void GreedyPatternRewriteDriver::notifyMatchFailure(
1099 Location loc, function_ref<void(Diagnostic &)> reasonCallback) {
1100 LLVM_DEBUG({
1101 Diagnostic diag(loc, DiagnosticSeverity::Remark);
1102 reasonCallback(diag);
1103 logger.startLine() << "** Match Failure : " << diag.str() << "\n";
1104 });
1105 if (RewriterBase::Listener *listener = config.getListener())
1106 listener->notifyMatchFailure(loc, reasonCallback);
1107}
1108
1109//===----------------------------------------------------------------------===//
1110// RegionPatternRewriteDriver
1111//===----------------------------------------------------------------------===//
1112
1113namespace {
1114/// This driver simplfies all ops in a region. If a scope is set in the
1115/// config, the provided region must be within that scope.
1116class RegionPatternRewriteDriver : public GreedyPatternRewriteDriver {
1117public:
1118 explicit RegionPatternRewriteDriver(MLIRContext *ctx,
1119 const FrozenRewritePatternSet &patterns,
1120 const GreedyRewriteConfig &config,
1121 Region &regions);
1122
1123 /// Simplify ops inside `region` and simplify the region itself. Return
1124 /// success if the transformation converged.
1125 LogicalResult simplify(bool *changed) &&;
1126
1127private:
1128 /// The region that is simplified.
1129 Region &region;
1130};
1131} // namespace
1132
1133RegionPatternRewriteDriver::RegionPatternRewriteDriver(
1134 MLIRContext *ctx, const FrozenRewritePatternSet &patterns,
1135 const GreedyRewriteConfig &config, Region &region)
1136 : GreedyPatternRewriteDriver(ctx, patterns, config), region(region) {
1137 // Populate strict mode ops.
1139 region.walk([&](Operation *op) { strictModeFilteredOps.insert(op); });
1140 }
1141#ifndef NDEBUG
1142 // Verify that the region is within the configured scope (if any).
1143 if (Region *scope = config.getScope()) {
1144 Region *r = &region;
1145 while (r && r != scope)
1146 r = r->getParentRegion();
1147 assert(r && "provided region is not within the config scope");
1148 }
1149#endif
1150}
1151
1152namespace {
1153class GreedyPatternRewriteIteration
1154 : public tracing::ActionImpl<GreedyPatternRewriteIteration> {
1155public:
1156 MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(GreedyPatternRewriteIteration)
1157 GreedyPatternRewriteIteration(ArrayRef<IRUnit> units, int64_t iteration)
1158 : tracing::ActionImpl<GreedyPatternRewriteIteration>(units),
1159 iteration(iteration) {}
1160 static constexpr StringLiteral tag = "GreedyPatternRewriteIteration";
1161 void print(raw_ostream &os) const override {
1162 os << "GreedyPatternRewriteIteration(" << iteration << ")";
1163 }
1164
1165private:
1166 int64_t iteration = 0;
1167};
1168} // namespace
1169
1170LogicalResult RegionPatternRewriteDriver::simplify(bool *changed) && {
1171 bool continueRewrites = false;
1172 bool anyChanged = false;
1173 int64_t iteration = 0;
1174 MLIRContext *ctx = rewriter.getContext();
1175 do {
1176 // Check if the iteration limit was reached.
1177 if (++iteration > config.getMaxIterations() &&
1179 break;
1180
1181 // New iteration: start with an empty worklist.
1182 worklist.clear();
1183
1184 // `OperationFolder` CSE's constant ops (and may move them into parents
1185 // regions to enable more aggressive CSE'ing). It must notify the
1186 // reachability listener, which forwards to the driver, so that the
1187 // reachability cache sees every CFG change.
1188 OperationFolder folder(ctx, &reachabilityListener);
1189 auto insertKnownConstant = [&](Operation *op) {
1190 // Check for existing constants when populating the worklist. This avoids
1191 // accidentally reversing the constant order during processing.
1192 Attribute constValue;
1193 if (!matchPattern(op, m_Constant(&constValue)))
1194 return false;
1195 auto result = folder.insertKnownConstant(op, constValue);
1196 anyChanged |=
1199 };
1200
1201 if (!config.getUseTopDownTraversal()) {
1202 // Add operations to the worklist in postorder.
1203 region.walk([&](Operation *op) {
1204 if (!config.isConstantCSEEnabled() || !insertKnownConstant(op))
1205 addToWorklist(op);
1206 });
1207 } else {
1208 // Add all nested operations to the worklist in preorder.
1209 region.walk<WalkOrder::PreOrder>([&](Operation *op) {
1210 if (!config.isConstantCSEEnabled() || !insertKnownConstant(op)) {
1211 addToWorklist(op);
1212 return WalkResult::advance();
1213 }
1214 return WalkResult::skip();
1215 });
1216
1217 // Reverse the list so our pop-back loop processes them in-order.
1218 worklist.reverse();
1219 }
1220
1221 ctx->executeAction<GreedyPatternRewriteIteration>(
1222 [&] {
1223 continueRewrites = false;
1224
1225 // Erase unreachable blocks
1226 // Operations like:
1227 // %add = arith.addi %add, %add : i64
1228 // are legal in unreachable code. Unfortunately many patterns would be
1229 // unsafe to apply on such IR and can lead to crashes or infinite
1230 // loops. A rewrite can disconnect more blocks while processWorklist()
1231 // runs; processWorklist() skips their queued operations until the
1232 // next sweep, since repeated rewrites on such IR can prevent the
1233 // worklist pass from returning.
1234 continueRewrites |=
1235 succeeded(eraseUnreachableBlocks(rewriter, region));
1236
1237 continueRewrites |= processWorklist();
1238
1239 // After applying patterns, make sure that the CFG of each of the
1240 // regions is kept up to date.
1241 if (config.getRegionSimplificationLevel() !=
1243 continueRewrites |= succeeded(simplifyRegions(
1244 rewriter, region,
1245 /*mergeBlocks=*/config.getRegionSimplificationLevel() ==
1247 }
1248
1249 // Optionally run full CSE. If CSE changes the IR we iterate again so
1250 // that patterns can fire on the deduplicated operations.
1251 if (config.isCSEBetweenIterationsEnabled()) {
1252 DominanceInfo domInfo;
1253 bool cseChanged = false;
1254 eliminateCommonSubExpressions(rewriter, domInfo, region,
1255 &cseChanged);
1256 continueRewrites |= cseChanged;
1257 }
1258 },
1259 {&region}, iteration);
1260 anyChanged |= continueRewrites;
1261 } while (continueRewrites);
1262
1263 if (changed)
1264 *changed = anyChanged;
1265
1266 // Whether the rewrite converges, i.e. wasn't changed in the last iteration.
1267 return success(!continueRewrites);
1268}
1269
1270LogicalResult
1272 const FrozenRewritePatternSet &patterns,
1273 GreedyRewriteConfig config, bool *changed) {
1274 // The top-level operation must be known to be isolated from above to
1275 // prevent performing canonicalizations on operations defined at or above
1276 // the region containing 'op'.
1278 "patterns can only be applied to operations IsolatedFromAbove");
1279
1280 // Set scope if not specified.
1281 if (!config.getScope())
1282 config.setScope(&region);
1283
1284#if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
1285 if (failed(verify(config.getScope()->getParentOp())))
1286 llvm::report_fatal_error(
1287 "greedy pattern rewriter input IR failed to verify");
1288#endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
1289
1290#ifdef EXPENSIVE_CHECKS
1291 OperationFingerPrint before(config.getScope()->getParentOp());
1292#endif // EXPENSIVE_CHECKS
1293 bool rewriteChanged = false;
1294
1295 // Start the pattern driver.
1296 RegionPatternRewriteDriver driver(region.getContext(), patterns, config,
1297 region);
1298 LogicalResult converged = std::move(driver).simplify(&rewriteChanged);
1299#ifdef EXPENSIVE_CHECKS
1300 if (rewriteChanged !=
1301 (before != OperationFingerPrint(config.getScope()->getParentOp())))
1302 llvm::report_fatal_error(
1303 "greedy pattern rewriter changed flag does not match the IR");
1304#endif // EXPENSIVE_CHECKS
1305 if (changed)
1306 *changed = rewriteChanged;
1307 if (failed(converged))
1308 LDBG() << "The pattern rewrite did not converge after scanning "
1309 << config.getMaxIterations() << " times";
1310 return converged;
1311}
1312
1313//===----------------------------------------------------------------------===//
1314// MultiOpPatternRewriteDriver
1315//===----------------------------------------------------------------------===//
1316
1317namespace {
1318/// This driver simplfies a list of ops.
1319class MultiOpPatternRewriteDriver : public GreedyPatternRewriteDriver {
1320public:
1321 explicit MultiOpPatternRewriteDriver(
1322 MLIRContext *ctx, const FrozenRewritePatternSet &patterns,
1323 const GreedyRewriteConfig &config, ArrayRef<Operation *> ops,
1324 llvm::SmallDenseSet<Operation *, 4> *survivingOps = nullptr);
1325
1326 /// Simplify `ops`. Return `success` if the transformation converged.
1327 LogicalResult simplify(ArrayRef<Operation *> ops, bool *changed = nullptr) &&;
1328
1329private:
1330 void notifyOperationErased(Operation *op) override {
1331 GreedyPatternRewriteDriver::notifyOperationErased(op);
1332 if (survivingOps)
1333 survivingOps->erase(op);
1334 }
1335
1336 /// An optional set of ops that survived the rewrite. This set is populated
1337 /// at the beginning of `simplifyLocally` with the inititally provided list
1338 /// of ops.
1339 llvm::SmallDenseSet<Operation *, 4> *const survivingOps = nullptr;
1340};
1341} // namespace
1342
1343MultiOpPatternRewriteDriver::MultiOpPatternRewriteDriver(
1344 MLIRContext *ctx, const FrozenRewritePatternSet &patterns,
1345 const GreedyRewriteConfig &config, ArrayRef<Operation *> ops,
1346 llvm::SmallDenseSet<Operation *, 4> *survivingOps)
1347 : GreedyPatternRewriteDriver(ctx, patterns, config),
1348 survivingOps(survivingOps) {
1350 strictModeFilteredOps.insert_range(ops);
1351
1352 if (survivingOps) {
1353 survivingOps->clear();
1354 survivingOps->insert_range(ops);
1355 }
1356}
1357
1358LogicalResult MultiOpPatternRewriteDriver::simplify(ArrayRef<Operation *> ops,
1359 bool *changed) && {
1360 // Populate the initial worklist.
1361 for (Operation *op : ops)
1362 addSingleOpToWorklist(op);
1363
1364 // Process ops on the worklist.
1365 bool result = processWorklist();
1366 if (changed)
1367 *changed = result;
1368
1369 return success(worklist.empty());
1370}
1371
1372/// Find the region that is the closest common ancestor of all given ops.
1373///
1374/// Note: This function returns `nullptr` if there is a top-level op among the
1375/// given list of ops.
1377 assert(!ops.empty() && "expected at least one op");
1378 // Fast path in case there is only one op.
1379 if (ops.size() == 1)
1380 return ops.front()->getParentRegion();
1381
1382 Region *region = ops.front()->getParentRegion();
1383 ops = ops.drop_front();
1384 int sz = ops.size();
1385 llvm::BitVector remainingOps(sz, true);
1386 while (region) {
1387 int pos = -1;
1388 // Iterate over all remaining ops.
1389 while ((pos = remainingOps.find_first_in(pos + 1, sz)) != -1) {
1390 // Is this op contained in `region`?
1391 if (region->findAncestorOpInRegion(*ops[pos]))
1392 remainingOps.reset(pos);
1393 }
1394 if (remainingOps.none())
1395 break;
1396 region = region->getParentRegion();
1397 }
1398 return region;
1399}
1400
1403 GreedyRewriteConfig config, bool *changed, bool *allErased) {
1404 if (ops.empty()) {
1405 if (changed)
1406 *changed = false;
1407 if (allErased)
1408 *allErased = true;
1409 return success();
1410 }
1411
1412 // Determine scope of rewrite.
1413 if (!config.getScope()) {
1414 // Compute scope if none was provided. The scope will remain `nullptr` if
1415 // there is a top-level op among `ops`.
1416 config.setScope(findCommonAncestor(ops));
1417 } else {
1418 // If a scope was provided, make sure that all ops are in scope.
1419#ifndef NDEBUG
1420 bool allOpsInScope = llvm::all_of(ops, [&](Operation *op) {
1421 return static_cast<bool>(config.getScope()->findAncestorOpInRegion(*op));
1422 });
1423 assert(allOpsInScope && "ops must be within the specified scope");
1424#endif // NDEBUG
1425 }
1426
1427#if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
1428 if (config.getScope() && failed(verify(config.getScope()->getParentOp())))
1429 llvm::report_fatal_error(
1430 "greedy pattern rewriter input IR failed to verify");
1431#endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
1432
1433#ifdef EXPENSIVE_CHECKS
1434 std::optional<OperationFingerPrint> before;
1435 if (config.getScope())
1436 before.emplace(config.getScope()->getParentOp());
1437#endif // EXPENSIVE_CHECKS
1438 bool rewriteChanged = false;
1439
1440 // Start the pattern driver.
1441 llvm::SmallDenseSet<Operation *, 4> surviving;
1442 MultiOpPatternRewriteDriver driver(ops.front()->getContext(), patterns,
1443 config, ops,
1444 allErased ? &surviving : nullptr);
1445 LogicalResult converged = std::move(driver).simplify(ops, &rewriteChanged);
1446#ifdef EXPENSIVE_CHECKS
1447 if (before &&
1448 rewriteChanged !=
1449 (*before != OperationFingerPrint(config.getScope()->getParentOp())))
1450 llvm::report_fatal_error(
1451 "greedy pattern rewriter changed flag does not match the IR");
1452#endif // EXPENSIVE_CHECKS
1453 if (changed)
1454 *changed = rewriteChanged;
1455 if (allErased)
1456 *allErased = surviving.empty();
1457 if (failed(converged))
1458 LDBG() << "The pattern rewrite did not converge after "
1459 << config.getMaxNumRewrites() << " rewrites";
1460 return converged;
1461}
return success()
if(failed(verifyVectorMemoryOp(getOperation(), memrefType, getVectorType()))) return failure()
static bool mayDefineSuccessors(Operation *op)
Return whether op may define successors of its block.
static Region * findCommonAncestor(ArrayRef< Operation * > ops)
Find the region that is the closest common ancestor of all given ops.
static SmallPtrSet< Block *, 2 > getRegionSuccessors(Block *block)
Collect the distinct successors of block that are in the same region.
#define DEBUG_TYPE
*if copies could not be generated due to yet unimplemented cases *copyInPlacementStart and copyOutPlacementStart in copyPlacementBlock *specify the insertion points where the incoming copies and outgoing should be inserted(the insertion happens right before the *insertion point). Since `begin` can itself be invalidated due to the memref *rewriting done from this method
*if copies could not be generated due to yet unimplemented cases *copyInPlacementStart and copyOutPlacementStart in copyPlacementBlock *specify the insertion points where the incoming copies and outgoing should be the output argument nBegin is set to its * replacement(set to `begin` if no invalidation happens). Since outgoing *copies could have been inserted at `end`
static const mlir::GenInfo * generator
values clear()
static Operation * getDumpRootOp(Operation *op)
Log IR after pattern application.
static void print(spirv::VerCapExtAttr triple, DialectAsmPrinter &printer)
#define MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(CLASS_NAME)
Definition TypeID.h:331
static Operation * findAncestorOpInRegion(Region *region, Operation *op)
Return the ancestor op in the region or nullptr if the region is not an ancestor of the op.
Attributes are known-constant values of operations.
Definition Attributes.h:25
Block represents an ordered list of Operations.
Definition Block.h:34
Region * getParent() const
Provide a 'getParent' method for ilist_node_with_parent methods.
Definition Block.cpp:27
SuccessorRange getSuccessors()
Definition Block.h:280
bool isReachable(Block *other, SmallPtrSet< Block *, 16 > &&except={})
Return "true" if there is a path from this block to the given block (according to the successors rela...
Definition Block.cpp:368
bool hasNoPredecessors()
Return true if this block has no predecessors.
Definition Block.h:255
Operation * getParentOp()
Returns the closest surrounding operation that contains this block.
Definition Block.cpp:31
This class contains all of the information necessary to report a diagnostic to the DiagnosticEngine.
virtual Operation * materializeConstant(OpBuilder &builder, Attribute value, Type type, Location loc)
Registered hook to materialize a single constant operation from a given attribute value with the desi...
Definition Dialect.h:83
A class for computing basic dominance information.
Definition Dominance.h:143
This class represents a frozen set of patterns that can be processed by a pattern applicator.
This class allows control over how the GreedyPatternRewriteDriver works.
static constexpr int64_t kNoLimit
bool isFoldingEnabled() const
Whether this should fold while greedily rewriting.
RewriterBase::Listener * getListener() const
An optional listener that should be notified about IR modifications.
bool isConstantCSEEnabled() const
If set to "true", constants are CSE'd (even across multiple regions that are in a parent-ancestor rel...
Region * getScope() const
Only ops within the scope are added to the worklist.
GreedyRewriteStrictness getStrictness() const
Strict mode can restrict the ops that are added to the worklist during the rewrite.
bool isCSEBetweenIterationsEnabled() const
If set to "true", full common-subexpression elimination is run on the scoped region between each patt...
bool getUseTopDownTraversal() const
This specifies the order of initial traversal that populates the rewriters worklist.
GreedyRewriteConfig & setScope(Region *scope)
GreedyRewriteConfig & setListener(RewriterBase::Listener *listener)
int64_t getMaxNumRewrites() const
This specifies the maximum number of rewrites within an iteration.
int64_t getMaxIterations() const
This specifies the maximum number of times the rewriter will iterate between applying patterns and si...
GreedySimplifyRegionLevel getRegionSimplificationLevel() const
Perform control flow optimizations to the region tree after applying all patterns.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:64
void executeAction(function_ref< void()> actionFn, const tracing::Action &action)
Dispatch the provided action to the handler if any, or just execute it.
This class represents a saved insertion point.
Definition Builders.h:330
bool isSet() const
Returns true if this insert point is set.
Definition Builders.h:340
Block * getBlock() const
Definition Builders.h:342
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:401
Set of flags used to control the behavior of the various IR print methods (e.g.
This class provides the API for ops that are known to be isolated from above.
This class provides the API for ops that are known to be terminators.
A wrapper class that allows for printing an operation with a set of flags, useful to act as a "stream...
Definition Operation.h:1169
A unique fingerprint for a specific operation, and all of it's internal operations (if includeNested ...
A utility class for folding operations, and unifying duplicated constants generated along the way.
Definition FoldUtils.h:33
@ Unchanged
The operation was retained without modifying the IR.
Definition FoldUtils.h:51
@ Replaced
The operation was replaced with an existing constant and erased.
Definition FoldUtils.h:55
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Dialect * getDialect()
Return the dialect this operation is associated with, or nullptr if the associated dialect is not loa...
Definition Operation.h:237
LogicalResult fold(ArrayRef< Attribute > operands, SmallVectorImpl< OpFoldResult > &results)
Attempt to fold this operation with the specified constant operand values.
bool hasTrait()
Returns true if the operation was registered with a particular trait, e.g.
Definition Operation.h:801
unsigned getNumSuccessors()
Definition Operation.h:758
bool mightHaveTrait()
Returns true if the operation might have the provided trait.
Definition Operation.h:809
Block * getBlock()
Returns the operation block that contains this operation.
Definition Operation.h:230
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:432
unsigned getNumRegions()
Returns the number of regions held by this operation.
Definition Operation.h:726
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition Operation.h:251
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:115
void print(raw_ostream &os, const OpPrintingFlags &flags={})
MutableArrayRef< Region > getRegions()
Returns the regions held by this operation.
Definition Operation.h:729
result_type_range getResultTypes()
Definition Operation.h:453
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:403
std::enable_if_t< llvm::function_traits< std::decay_t< FnT > >::num_args==1, RetT > walk(FnT &&callback)
Walk the operation by calling the callback for each nested operation (including this one),...
Definition Operation.h:849
Region * getParentRegion()
Returns the region to which the instruction belongs.
Definition Operation.h:247
void erase()
Remove this operation from its parent block and delete it.
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:429
This class manages the application of a group of rewrite patterns, with a user-provided cost model.
LogicalResult matchAndRewrite(Operation *op, PatternRewriter &rewriter, function_ref< bool(const Pattern &)> canApply={}, function_ref< void(const Pattern &)> onFailure={}, function_ref< LogicalResult(const Pattern &)> onSuccess={})
Attempt to match and rewrite the given op with any pattern, allowing a predicate to decide if a patte...
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
Block & front()
Definition Region.h:65
Region * getParentRegion()
Return the region containing this region or nullptr if the region is attached to a top-level operatio...
Definition Region.cpp:45
Operation * findAncestorOpInRegion(Operation &op)
Returns 'op' if 'op' lies in this region, or otherwise finds the ancestor of 'op' that lies in this r...
Definition Region.cpp:168
Operation * getParentOp()
Return the parent operation this region is attached to.
Definition Region.h:198
MLIRContext * getContext()
Return the context this region is inserted in.
Definition Region.cpp:24
BlockListType::iterator iterator
Definition Region.h:52
RetT walk(FnT &&callback)
Walk all nested operations, blocks or regions (including this region), depending on the type of callb...
Definition Region.h:297
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
static WalkResult skip()
Definition WalkResult.h:48
static WalkResult advance()
Definition WalkResult.h:47
CRTP Implementation of an action.
Definition Action.h:76
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:734
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
Definition Matchers.h:490
void eliminateCommonSubExpressions(RewriterBase &rewriter, DominanceInfo &domInfo, Operation *op, bool *changed=nullptr, int64_t *numCSE=nullptr, int64_t *numDCE=nullptr)
Eliminate common subexpressions within the given operation.
Definition CSE.cpp:415
LogicalResult eraseUnreachableBlocks(RewriterBase &rewriter, MutableArrayRef< Region > regions, bool recurse=true)
Erase the unreachable blocks within the provided regions.
@ Aggressive
Run extra simplificiations (e.g.
@ Disabled
Disable region control-flow simplification.
LogicalResult applyPatternsGreedily(Region &region, const FrozenRewritePatternSet &patterns, GreedyRewriteConfig config=GreedyRewriteConfig(), bool *changed=nullptr)
Rewrite ops in the given region, which must be isolated from above, by repeatedly applying the highes...
LogicalResult applyOpPatternsGreedily(ArrayRef< Operation * > ops, const FrozenRewritePatternSet &patterns, GreedyRewriteConfig config=GreedyRewriteConfig(), bool *changed=nullptr, bool *allErased=nullptr)
Rewrite the specified ops by repeatedly applying the highest benefit patterns in a greedy worklist dr...
bool isOpTriviallyDead(Operation *op)
Return true if the given operation is unused, and has no side effects on memory that prevent erasing.
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:123
@ AnyOp
No restrictions wrt. which ops are processed.
LogicalResult simplifyRegions(RewriterBase &rewriter, MutableArrayRef< Region > regions, bool mergeBlocks=true)
Run a set of structural simplifications over the given regions.
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
Definition Matchers.h:369
LogicalResult verify(Operation *op, bool verifyRecursively=true)
Perform (potentially expensive) checks of invariants, used to detect compiler bugs,...
Definition Verifier.cpp:566
llvm::function_ref< Fn > function_ref
Definition LLVM.h:155
A listener that forwards all notifications to another listener.
void notifyOperationInserted(Operation *op, InsertPoint previous) override
Notify the listener that the specified operation was inserted.
void notifyOperationModified(Operation *op) override
Notify the listener that the specified operation was modified in-place.
void notifyOperationErased(Operation *op) override
Notify the listener that the specified operation is about to be erased.
void notifyBlockErased(Block *block) override
Notify the listener that the specified block is about to be erased.
void notifyBlockInserted(Block *block, Region *previous, Region::iterator previousIt) override
Notify the listener that the specified block was inserted.