MLIR 23.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/Support/DebugLog.h"
31#include "llvm/Support/ScopedPrinter.h"
32#include "llvm/Support/raw_ostream.h"
33
34#ifdef MLIR_GREEDY_REWRITE_RANDOMIZER_SEED
35#include <random>
36#endif // MLIR_GREEDY_REWRITE_RANDOMIZER_SEED
37
38using namespace mlir;
39
40#define DEBUG_TYPE "greedy-rewriter"
41
42namespace {
43
44//===----------------------------------------------------------------------===//
45// Debugging Infrastructure
46//===----------------------------------------------------------------------===//
47
48#if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
49/// A helper struct that performs various "expensive checks" to detect broken
50/// rewrite patterns use the rewriter API incorrectly. A rewrite pattern is
51/// broken if:
52/// * IR does not verify after pattern application / folding.
53/// * Pattern returns "failure" but the IR has changed.
54/// * Pattern returns "success" but the IR has not changed.
55///
56/// This struct stores finger prints of ops to determine whether the IR has
57/// changed or not.
58struct ExpensiveChecks : public RewriterBase::ForwardingListener {
59 ExpensiveChecks(RewriterBase::Listener *driver, Operation *topLevel)
60 : RewriterBase::ForwardingListener(driver), topLevel(topLevel) {}
61
62 /// Compute finger prints of the given op and its nested ops.
63 void computeFingerPrints(Operation *topLevel) {
64 this->topLevel = topLevel;
65 this->topLevelFingerPrint.emplace(topLevel);
66 topLevel->walk([&](Operation *op) {
67 fingerprints.try_emplace(op, op, /*includeNested=*/false);
68 });
69 }
70
71 /// Clear all finger prints.
72 void clear() {
73 topLevel = nullptr;
74 topLevelFingerPrint.reset();
75 fingerprints.clear();
76 }
77
78 void notifyRewriteSuccess() {
79 if (!topLevel)
80 return;
81
82 // Make sure that the IR still verifies.
83 if (failed(verify(topLevel)))
84 llvm::report_fatal_error("IR failed to verify after pattern application");
85
86 // Pattern application success => IR must have changed.
87 OperationFingerPrint afterFingerPrint(topLevel);
88 if (*topLevelFingerPrint == afterFingerPrint) {
89 // Note: Run "mlir-opt -debug" to see which pattern is broken.
90 llvm::report_fatal_error(
91 "pattern returned success but IR did not change");
92 }
93 for (const auto &it : fingerprints) {
94 // Skip top-level op, its finger print is never invalidated.
95 if (it.first == topLevel)
96 continue;
97 // Note: Finger print computation may crash when an op was erased
98 // without notifying the rewriter. (Run with ASAN to see where the op was
99 // erased; the op was probably erased directly, bypassing the rewriter
100 // API.) Finger print computation does may not crash if a new op was
101 // created at the same memory location. (But then the finger print should
102 // have changed.)
103 if (it.second !=
104 OperationFingerPrint(it.first, /*includeNested=*/false)) {
105 // Note: Run "mlir-opt -debug" to see which pattern is broken.
106 llvm::report_fatal_error("operation finger print changed");
107 }
108 }
109 }
110
111 void notifyRewriteFailure() {
112 if (!topLevel)
113 return;
114
115 // Pattern application failure => IR must not have changed.
116 OperationFingerPrint afterFingerPrint(topLevel);
117 if (*topLevelFingerPrint != afterFingerPrint) {
118 // Note: Run "mlir-opt -debug" to see which pattern is broken.
119 llvm::report_fatal_error("pattern returned failure but IR did change");
120 }
121 }
122
123 void notifyFoldingSuccess() {
124 if (!topLevel)
125 return;
126
127 // Make sure that the IR still verifies.
128 if (failed(verify(topLevel)))
129 llvm::report_fatal_error("IR failed to verify after folding");
130 }
131
132protected:
133 /// Invalidate the finger print of the given op, i.e., remove it from the map.
134 void invalidateFingerPrint(Operation *op) { fingerprints.erase(op); }
135
136 void notifyBlockErased(Block *block) override {
138
139 // The block structure (number of blocks, types of block arguments, etc.)
140 // is part of the fingerprint of the parent op.
141 // TODO: The parent op fingerprint should also be invalidated when modifying
142 // the block arguments of a block, but we do not have a
143 // `notifyBlockModified` callback yet.
144 invalidateFingerPrint(block->getParentOp());
145 }
146
148 OpBuilder::InsertPoint previous) override {
150 invalidateFingerPrint(op->getParentOp());
151 }
152
153 void notifyOperationModified(Operation *op) override {
155 invalidateFingerPrint(op);
156 }
157
158 void notifyOperationErased(Operation *op) override {
160 op->walk([this](Operation *op) { invalidateFingerPrint(op); });
161 }
162
163 /// Operation finger prints to detect invalid pattern API usage. IR is checked
164 /// against these finger prints after pattern application to detect cases
165 /// where IR was modified directly, bypassing the rewriter API.
167
168 /// Top-level operation of the current greedy rewrite.
169 Operation *topLevel = nullptr;
170
171 /// Finger print of the top-level operation.
172 std::optional<OperationFingerPrint> topLevelFingerPrint;
173};
174#endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
175
176#ifndef NDEBUG
177static Operation *getDumpRootOp(Operation *op) {
178 // Dump the parent op so that materialized constants are visible. If the op
179 // is a top-level op, dump it directly.
180 if (Operation *parentOp = op->getParentOp())
181 return parentOp;
182 return op;
183}
184static void logSuccessfulFolding(Operation *op) {
185 LDBG() << "// *** IR Dump After Successful Folding ***\n"
186 << OpWithFlags(op, OpPrintingFlags().elideLargeElementsAttrs());
187}
188#endif // NDEBUG
189
190//===----------------------------------------------------------------------===//
191// Worklist
192//===----------------------------------------------------------------------===//
193
194/// A LIFO worklist of operations with efficient removal and set semantics.
195///
196/// This class maintains a vector of operations and a mapping of operations to
197/// positions in the vector, so that operations can be removed efficiently at
198/// random. When an operation is removed, it is replaced with nullptr. Such
199/// nullptr are skipped when pop'ing elements.
200class Worklist {
201public:
202 Worklist();
203
204 /// Clear the worklist.
205 void clear();
206
207 /// Return whether the worklist is empty.
208 bool empty() const;
209
210 /// Push an operation to the end of the worklist, unless the operation is
211 /// already on the worklist.
212 void push(Operation *op);
213
214 /// Pop the an operation from the end of the worklist. Only allowed on
215 /// non-empty worklists.
216 Operation *pop();
217
218 /// Remove an operation from the worklist.
219 void remove(Operation *op);
220
221 /// Reverse the worklist.
222 void reverse();
223
224protected:
225 /// The worklist of operations.
226 std::vector<Operation *> list;
227
228 /// A mapping of operations to positions in `list`.
230};
231
232Worklist::Worklist() { list.reserve(64); }
233
234void Worklist::clear() {
235 list.clear();
236 map.clear();
237}
238
239bool Worklist::empty() const {
240 // Skip all nullptr.
241 return !llvm::any_of(list,
242 [](Operation *op) { return static_cast<bool>(op); });
243}
244
245void Worklist::push(Operation *op) {
246 assert(op && "cannot push nullptr to worklist");
247 // Check to see if the worklist already contains this op.
248 if (!map.insert({op, list.size()}).second)
249 return;
250 list.push_back(op);
251}
252
253Operation *Worklist::pop() {
254 assert(!empty() && "cannot pop from empty worklist");
255 // Skip and remove all trailing nullptr.
256 while (!list.back())
257 list.pop_back();
258 Operation *op = list.back();
259 list.pop_back();
260 map.erase(op);
261 // Cleanup: Remove all trailing nullptr.
262 while (!list.empty() && !list.back())
263 list.pop_back();
264 return op;
265}
266
267void Worklist::remove(Operation *op) {
268 assert(op && "cannot remove nullptr from worklist");
269 auto it = map.find(op);
270 if (it != map.end()) {
271 assert(list[it->second] == op && "malformed worklist data structure");
272 list[it->second] = nullptr;
273 map.erase(it);
274 }
275}
276
277void Worklist::reverse() {
278 std::reverse(list.begin(), list.end());
279 for (size_t i = 0, e = list.size(); i != e; ++i)
280 map[list[i]] = i;
281}
282
283#ifdef MLIR_GREEDY_REWRITE_RANDOMIZER_SEED
284/// A worklist that pops elements at a random position. This worklist is for
285/// testing/debugging purposes only. It can be used to ensure that lowering
286/// pipelines work correctly regardless of the order in which ops are processed
287/// by the GreedyPatternRewriteDriver.
288class RandomizedWorklist : public Worklist {
289public:
290 RandomizedWorklist() : Worklist() {
291 generator.seed(MLIR_GREEDY_REWRITE_RANDOMIZER_SEED);
292 }
293
294 /// Pop a random non-empty op from the worklist.
295 Operation *pop() {
296 Operation *op = nullptr;
297 do {
298 assert(!list.empty() && "cannot pop from empty worklist");
299 int64_t pos = generator() % list.size();
300 op = list[pos];
301 list.erase(list.begin() + pos);
302 for (int64_t i = pos, e = list.size(); i < e; ++i)
303 map[list[i]] = i;
304 map.erase(op);
305 } while (!op);
306 return op;
307 }
308
309private:
310 std::minstd_rand0 generator;
311};
312#endif // MLIR_GREEDY_REWRITE_RANDOMIZER_SEED
313
314//===----------------------------------------------------------------------===//
315// GreedyPatternRewriteDriver
316//===----------------------------------------------------------------------===//
317
318/// This is a worklist-driven driver for the PatternMatcher, which repeatedly
319/// applies the locally optimal patterns.
320///
321/// This abstract class manages the worklist and contains helper methods for
322/// rewriting ops on the worklist. Derived classes specify how ops are added
323/// to the worklist in the beginning.
324class GreedyPatternRewriteDriver : public RewriterBase::Listener {
325protected:
326 explicit GreedyPatternRewriteDriver(MLIRContext *ctx,
327 const FrozenRewritePatternSet &patterns,
328 const GreedyRewriteConfig &config);
329
330 /// Add the given operation to the worklist.
331 void addSingleOpToWorklist(Operation *op);
332
333 /// Add the given operation and its ancestors to the worklist.
334 void addToWorklist(Operation *op);
335
336 /// Notify the driver that the specified operation may have been modified
337 /// in-place. The operation is added to the worklist.
338 void notifyOperationModified(Operation *op) override;
339
340 /// Notify the driver that the specified operation was inserted. Update the
341 /// worklist as needed: The operation is enqueued depending on scope and
342 /// strict mode.
343 void notifyOperationInserted(Operation *op,
344 OpBuilder::InsertPoint previous) override;
345
346 /// Notify the driver that the specified operation was removed. Update the
347 /// worklist as needed: The operation and its children are removed from the
348 /// worklist.
349 void notifyOperationErased(Operation *op) override;
350
351 /// Notify the driver that the specified operation was replaced. Update the
352 /// worklist as needed: New users are added enqueued.
353 void notifyOperationReplaced(Operation *op, ValueRange replacement) override;
354
355 /// Process ops until the worklist is empty or `config.maxNumRewrites` is
356 /// reached. Return `true` if any IR was changed.
357 bool processWorklist();
358
359 /// The pattern rewriter that is used for making IR modifications and is
360 /// passed to rewrite patterns.
361 PatternRewriter rewriter;
362
363 /// The worklist for this transformation keeps track of the operations that
364 /// need to be (re)visited.
365#ifdef MLIR_GREEDY_REWRITE_RANDOMIZER_SEED
366 RandomizedWorklist worklist;
367#else
368 Worklist worklist;
369#endif // MLIR_GREEDY_REWRITE_RANDOMIZER_SEED
370
371 /// Configuration information for how to simplify.
372 const GreedyRewriteConfig config;
373
374 /// The list of ops we are restricting our rewrites to. These include the
375 /// supplied set of ops as well as new ops created while rewriting those ops
376 /// depending on `strictMode`. This set is not maintained when
377 /// `config.strictMode` is GreedyRewriteStrictness::AnyOp.
378 llvm::SmallDenseSet<Operation *, 4> strictModeFilteredOps;
379
380private:
381 /// Look over the provided operands for any defining operations that should
382 /// be re-added to the worklist. This function should be called when an
383 /// operation is modified or removed, as it may trigger further
384 /// simplifications.
385 void addOperandsToWorklist(Operation *op);
386
387 /// Notify the driver that the given block was inserted.
388 void notifyBlockInserted(Block *block, Region *previous,
389 Region::iterator previousIt) override;
390
391 /// Notify the driver that the given block is about to be removed.
392 void notifyBlockErased(Block *block) override;
393
394 /// For debugging only: Notify the driver of a pattern match failure.
395 void
396 notifyMatchFailure(Location loc,
397 function_ref<void(Diagnostic &)> reasonCallback) override;
398
399#ifndef NDEBUG
400 /// A raw output stream used to prefix the debug log.
401
402 llvm::impl::raw_ldbg_ostream os{(Twine("[") + DEBUG_TYPE + ":1] ").str(),
403 llvm::dbgs()};
404 /// A logger used to emit information during the application process.
405 llvm::ScopedPrinter logger{os};
406#endif
407
408 /// The low-level pattern applicator.
409 PatternApplicator matcher;
410
411#if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
412 ExpensiveChecks expensiveChecks;
413#endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
414};
415} // namespace
416
417GreedyPatternRewriteDriver::GreedyPatternRewriteDriver(
418 MLIRContext *ctx, const FrozenRewritePatternSet &patterns,
419 const GreedyRewriteConfig &config)
420 : rewriter(ctx), config(config), matcher(patterns)
421#if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
422 // clang-format off
423 , expensiveChecks(
424 /*driver=*/this,
425 /*topLevel=*/config.getScope() ? config.getScope()->getParentOp()
426 : nullptr)
427// clang-format on
428#endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
429{
430 // Apply a simple cost model based solely on pattern benefit.
431 matcher.applyDefaultCostModel();
432
433 // Set up listener.
434#if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
435 // Send IR notifications to the debug handler. This handler will then forward
436 // all notifications to this GreedyPatternRewriteDriver.
437 rewriter.setListener(&expensiveChecks);
438#else
439 rewriter.setListener(this);
440#endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
441}
442
443bool GreedyPatternRewriteDriver::processWorklist() {
444#ifndef NDEBUG
445 const char *logLineComment =
446 "//===-------------------------------------------===//\n";
447
448 /// A utility function to log a process result for the given reason.
449 auto logResult = [&](StringRef result, const llvm::Twine &msg = {}) {
450 logger.unindent();
451 logger.startLine() << "} -> " << result;
452 if (!msg.isTriviallyEmpty())
453 logger.getOStream() << " : " << msg;
454 logger.getOStream() << "\n";
455 };
456 auto logResultWithLine = [&](StringRef result, const llvm::Twine &msg = {}) {
457 logResult(result, msg);
458 logger.startLine() << logLineComment;
459 };
460#endif
461
462 bool changed = false;
463 int64_t numRewrites = 0;
464 while (!worklist.empty() &&
465 (numRewrites < config.getMaxNumRewrites() ||
467 auto *op = worklist.pop();
468
469 LLVM_DEBUG({
470 logger.getOStream() << "\n";
471 logger.startLine() << logLineComment;
472 logger.startLine() << "Processing operation : '" << op->getName() << "'("
473 << op << ") {\n";
474 logger.indent();
475
476 // If the operation has no regions, just print it here.
477 if (op->getNumRegions() == 0) {
478 op->print(
479 logger.startLine(),
480 OpPrintingFlags().printGenericOpForm().elideLargeElementsAttrs());
481 logger.getOStream() << "\n\n";
482 }
483 });
484
485 // If the operation is trivially dead - remove it.
486 if (isOpTriviallyDead(op)) {
487 rewriter.eraseOp(op);
488 changed = true;
489
490 LLVM_DEBUG(logResultWithLine("success", "operation is trivially dead"));
491 continue;
492 }
493
494 // Try to fold this op. Do not fold constant ops. That would lead to an
495 // infinite folding loop, as every constant op would be folded to an
496 // Attribute and then immediately be rematerialized as a constant op, which
497 // is then put on the worklist.
498 if (config.isFoldingEnabled() && !op->hasTrait<OpTrait::ConstantLike>()) {
499 SmallVector<OpFoldResult> foldResults;
500 if (succeeded(op->fold(foldResults))) {
501 LLVM_DEBUG(logResultWithLine("success", "operation was folded"));
502#ifndef NDEBUG
503 Operation *dumpRootOp = getDumpRootOp(op);
504#endif // NDEBUG
505 if (foldResults.empty()) {
506 // Op was modified in-place.
507 notifyOperationModified(op);
508 changed = true;
509 LLVM_DEBUG(logSuccessfulFolding(dumpRootOp));
510#if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
511 expensiveChecks.notifyFoldingSuccess();
512#endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
513 continue;
514 }
515
516 // Op results can be replaced with `foldResults`.
517 assert(foldResults.size() == op->getNumResults() &&
518 "folder produced incorrect number of results");
519 OpBuilder::InsertionGuard g(rewriter);
520 rewriter.setInsertionPoint(op);
521 SmallVector<Value> replacements;
522 bool materializationSucceeded = true;
523 for (auto [ofr, resultType] :
524 llvm::zip_equal(foldResults, op->getResultTypes())) {
525 if (auto value = dyn_cast<Value>(ofr)) {
526 assert(value.getType() == resultType &&
527 "folder produced value of incorrect type");
528 replacements.push_back(value);
529 continue;
530 }
531 // Materialize Attributes as SSA values.
532 Operation *constOp = op->getDialect()->materializeConstant(
533 rewriter, cast<Attribute>(ofr), resultType, op->getLoc());
534
535 if (!constOp) {
536 // If materialization fails, cleanup any operations generated for
537 // the previous results.
538 llvm::SmallDenseSet<Operation *> replacementOps;
539 for (Value replacement : replacements) {
540 assert(replacement.use_empty() &&
541 "folder reused existing op for one result but constant "
542 "materialization failed for another result");
543 replacementOps.insert(replacement.getDefiningOp());
544 }
545 for (Operation *op : replacementOps) {
546 rewriter.eraseOp(op);
547 }
548
549 materializationSucceeded = false;
550 break;
551 }
552
553 assert(constOp->hasTrait<OpTrait::ConstantLike>() &&
554 "materializeConstant produced op that is not a ConstantLike");
555 assert(constOp->getResultTypes()[0] == resultType &&
556 "materializeConstant produced incorrect result type");
557 replacements.push_back(constOp->getResult(0));
558 }
559
560 if (materializationSucceeded) {
561 rewriter.replaceOp(op, replacements);
562 changed = true;
563 LLVM_DEBUG(logSuccessfulFolding(dumpRootOp));
564#if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
565 expensiveChecks.notifyFoldingSuccess();
566#endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
567 continue;
568 }
569 }
570 }
571
572 // Try to match one of the patterns. The rewriter is automatically
573 // notified of any necessary changes, so there is nothing else to do
574 // here.
575 auto canApplyCallback = [&](const Pattern &pattern) {
576 LLVM_DEBUG({
577 logger.getOStream() << "\n";
578 logger.startLine() << "* Pattern " << pattern.getDebugName() << " : '"
579 << op->getName() << " -> (";
580 llvm::interleaveComma(pattern.getGeneratedOps(), logger.getOStream());
581 logger.getOStream() << ")' {\n";
582 logger.indent();
583 });
584 if (RewriterBase::Listener *listener = config.getListener())
585 listener->notifyPatternBegin(pattern, op);
586 return true;
587 };
588 function_ref<bool(const Pattern &)> canApply = canApplyCallback;
589 auto onFailureCallback = [&](const Pattern &pattern) {
590 LLVM_DEBUG(logResult("failure", "pattern failed to match"));
591 if (RewriterBase::Listener *listener = config.getListener())
592 listener->notifyPatternEnd(pattern, failure());
593 };
594 function_ref<void(const Pattern &)> onFailure = onFailureCallback;
595 auto onSuccessCallback = [&](const Pattern &pattern) {
596 LLVM_DEBUG(logResult("success", "pattern applied successfully"));
597 if (RewriterBase::Listener *listener = config.getListener())
598 listener->notifyPatternEnd(pattern, success());
599 return success();
600 };
601 function_ref<LogicalResult(const Pattern &)> onSuccess = onSuccessCallback;
602
603#ifdef NDEBUG
604 // Optimization: PatternApplicator callbacks are not needed when running in
605 // optimized mode and without a listener.
606 if (!config.getListener()) {
607 canApply = nullptr;
608 onFailure = nullptr;
609 onSuccess = nullptr;
610 }
611#endif // NDEBUG
612
613#if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
614 if (config.getScope()) {
615 expensiveChecks.computeFingerPrints(config.getScope()->getParentOp());
616 }
617 llvm::scope_exit clearFingerprints([&]() { expensiveChecks.clear(); });
618#endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
619
620 LogicalResult matchResult =
621 matcher.matchAndRewrite(op, rewriter, canApply, onFailure, onSuccess);
622
623 if (succeeded(matchResult)) {
624 LLVM_DEBUG(logResultWithLine("success", "at least one pattern matched"));
625#if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
626 expensiveChecks.notifyRewriteSuccess();
627#endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
628 changed = true;
629 ++numRewrites;
630 } else {
631 LLVM_DEBUG(logResultWithLine("failure", "all patterns failed to match"));
632#if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
633 expensiveChecks.notifyRewriteFailure();
634#endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
635 }
636 }
637
638 return changed;
639}
640
641void GreedyPatternRewriteDriver::addToWorklist(Operation *op) {
642 assert(op && "expected valid op");
643 // Gather potential ancestors while looking for a "scope" parent region.
644 SmallVector<Operation *, 8> ancestors;
645 Region *region = nullptr;
646 do {
647 ancestors.push_back(op);
648 region = op->getParentRegion();
649 if (config.getScope() == region) {
650 // Scope (can be `nullptr`) was reached. Stop traveral and enqueue ops.
651 for (Operation *op : ancestors)
652 addSingleOpToWorklist(op);
653 return;
654 }
655 if (region == nullptr)
656 return;
657 } while ((op = region->getParentOp()));
658}
659
660void GreedyPatternRewriteDriver::addSingleOpToWorklist(Operation *op) {
661 if (config.getStrictness() == GreedyRewriteStrictness::AnyOp ||
662 strictModeFilteredOps.contains(op))
663 worklist.push(op);
664}
665
666void GreedyPatternRewriteDriver::notifyBlockInserted(
667 Block *block, Region *previous, Region::iterator previousIt) {
668 if (RewriterBase::Listener *listener = config.getListener())
669 listener->notifyBlockInserted(block, previous, previousIt);
670}
671
672void GreedyPatternRewriteDriver::notifyBlockErased(Block *block) {
673 if (RewriterBase::Listener *listener = config.getListener())
674 listener->notifyBlockErased(block);
675}
676
677void GreedyPatternRewriteDriver::notifyOperationInserted(
678 Operation *op, OpBuilder::InsertPoint previous) {
679 LLVM_DEBUG({
680 logger.startLine() << "** Insert : '" << op->getName() << "'(" << op
681 << ")\n";
682 });
683 if (RewriterBase::Listener *listener = config.getListener())
684 listener->notifyOperationInserted(op, previous);
685 if (config.getStrictness() == GreedyRewriteStrictness::ExistingAndNewOps)
686 strictModeFilteredOps.insert(op);
687 addToWorklist(op);
688}
689
690void GreedyPatternRewriteDriver::notifyOperationModified(Operation *op) {
691 LLVM_DEBUG({
692 logger.startLine() << "** Modified: '" << op->getName() << "'(" << op
693 << ")\n";
694 });
695 if (RewriterBase::Listener *listener = config.getListener())
696 listener->notifyOperationModified(op);
697 addToWorklist(op);
698}
699
700void GreedyPatternRewriteDriver::addOperandsToWorklist(Operation *op) {
701 for (Value operand : op->getOperands()) {
702 // If this operand currently has at most 2 users, add its defining op to the
703 // worklist. Indeed, after the op is deleted, then the operand will have at
704 // most 1 user left. If it has 0 users left, it can be deleted too,
705 // and if it has 1 user left, there may be further canonicalization
706 // opportunities.
707 if (!operand)
708 continue;
709
710 auto *defOp = operand.getDefiningOp();
711 if (!defOp)
712 continue;
713
714 Operation *otherUser = nullptr;
715 bool hasMoreThanTwoUses = false;
716 for (auto *user : operand.getUsers()) {
717 if (user == op || user == otherUser)
718 continue;
719 if (!otherUser) {
720 otherUser = user;
721 continue;
722 }
723 hasMoreThanTwoUses = true;
724 break;
725 }
726 if (hasMoreThanTwoUses)
727 continue;
728
729 addToWorklist(defOp);
730 }
731}
732
733void GreedyPatternRewriteDriver::notifyOperationErased(Operation *op) {
734 LLVM_DEBUG({
735 logger.startLine() << "** Erase : '" << op->getName() << "'(" << op
736 << ")\n";
737 });
738
739#ifndef NDEBUG
740 // Only ops that are within the configured scope are added to the worklist of
741 // the greedy pattern rewriter. Moreover, the parent op of the scope region is
742 // the part of the IR that is taken into account for the "expensive checks".
743 // A greedy pattern rewrite is not allowed to erase the parent op of the scope
744 // region, as that would break the worklist handling and the expensive checks.
745 if (Region *scope = config.getScope(); scope->getParentOp() == op)
746 llvm_unreachable(
747 "scope region must not be erased during greedy pattern rewrite");
748#endif // NDEBUG
749
750 if (RewriterBase::Listener *listener = config.getListener())
751 listener->notifyOperationErased(op);
752
753 addOperandsToWorklist(op);
754 worklist.remove(op);
755
756 if (config.getStrictness() != GreedyRewriteStrictness::AnyOp)
757 strictModeFilteredOps.erase(op);
758}
759
760void GreedyPatternRewriteDriver::notifyOperationReplaced(
761 Operation *op, ValueRange replacement) {
762 LLVM_DEBUG({
763 logger.startLine() << "** Replace : '" << op->getName() << "'(" << op
764 << ")\n";
765 });
766 if (RewriterBase::Listener *listener = config.getListener())
767 listener->notifyOperationReplaced(op, replacement);
768}
769
770void GreedyPatternRewriteDriver::notifyMatchFailure(
771 Location loc, function_ref<void(Diagnostic &)> reasonCallback) {
772 LLVM_DEBUG({
773 Diagnostic diag(loc, DiagnosticSeverity::Remark);
774 reasonCallback(diag);
775 logger.startLine() << "** Match Failure : " << diag.str() << "\n";
776 });
777 if (RewriterBase::Listener *listener = config.getListener())
778 listener->notifyMatchFailure(loc, reasonCallback);
779}
780
781//===----------------------------------------------------------------------===//
782// RegionPatternRewriteDriver
783//===----------------------------------------------------------------------===//
784
785namespace {
786/// This driver simplfies all ops in a region. If a scope is set in the
787/// config, the provided region must be within that scope.
788class RegionPatternRewriteDriver : public GreedyPatternRewriteDriver {
789public:
790 explicit RegionPatternRewriteDriver(MLIRContext *ctx,
791 const FrozenRewritePatternSet &patterns,
792 const GreedyRewriteConfig &config,
793 Region &regions);
794
795 /// Simplify ops inside `region` and simplify the region itself. Return
796 /// success if the transformation converged.
797 LogicalResult simplify(bool *changed) &&;
798
799private:
800 /// The region that is simplified.
801 Region &region;
802};
803} // namespace
804
805RegionPatternRewriteDriver::RegionPatternRewriteDriver(
806 MLIRContext *ctx, const FrozenRewritePatternSet &patterns,
807 const GreedyRewriteConfig &config, Region &region)
808 : GreedyPatternRewriteDriver(ctx, patterns, config), region(region) {
809 // Populate strict mode ops.
811 region.walk([&](Operation *op) { strictModeFilteredOps.insert(op); });
812 }
813#ifndef NDEBUG
814 // Verify that the region is within the configured scope (if any).
815 if (Region *scope = config.getScope()) {
816 Region *r = &region;
817 while (r && r != scope)
818 r = r->getParentRegion();
819 assert(r && "provided region is not within the config scope");
820 }
821#endif
822}
823
824namespace {
825class GreedyPatternRewriteIteration
826 : public tracing::ActionImpl<GreedyPatternRewriteIteration> {
827public:
828 MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(GreedyPatternRewriteIteration)
829 GreedyPatternRewriteIteration(ArrayRef<IRUnit> units, int64_t iteration)
830 : tracing::ActionImpl<GreedyPatternRewriteIteration>(units),
831 iteration(iteration) {}
832 static constexpr StringLiteral tag = "GreedyPatternRewriteIteration";
833 void print(raw_ostream &os) const override {
834 os << "GreedyPatternRewriteIteration(" << iteration << ")";
835 }
836
837private:
838 int64_t iteration = 0;
839};
840} // namespace
841
842LogicalResult RegionPatternRewriteDriver::simplify(bool *changed) && {
843 bool continueRewrites = false;
844 int64_t iteration = 0;
845 MLIRContext *ctx = rewriter.getContext();
846 do {
847 // Check if the iteration limit was reached.
848 if (++iteration > config.getMaxIterations() &&
850 break;
851
852 // New iteration: start with an empty worklist.
853 worklist.clear();
854
855 // `OperationFolder` CSE's constant ops (and may move them into parents
856 // regions to enable more aggressive CSE'ing).
857 OperationFolder folder(ctx, this);
858 auto insertKnownConstant = [&](Operation *op) {
859 // Check for existing constants when populating the worklist. This avoids
860 // accidentally reversing the constant order during processing.
861 Attribute constValue;
862 if (matchPattern(op, m_Constant(&constValue)))
863 if (!folder.insertKnownConstant(op, constValue))
864 return true;
865 return false;
866 };
867
868 if (!config.getUseTopDownTraversal()) {
869 // Add operations to the worklist in postorder.
870 region.walk([&](Operation *op) {
871 if (!config.isConstantCSEEnabled() || !insertKnownConstant(op))
872 addToWorklist(op);
873 });
874 } else {
875 // Add all nested operations to the worklist in preorder.
876 region.walk<WalkOrder::PreOrder>([&](Operation *op) {
877 if (!config.isConstantCSEEnabled() || !insertKnownConstant(op)) {
878 addToWorklist(op);
879 return WalkResult::advance();
880 }
881 return WalkResult::skip();
882 });
883
884 // Reverse the list so our pop-back loop processes them in-order.
885 worklist.reverse();
886 }
887
888 ctx->executeAction<GreedyPatternRewriteIteration>(
889 [&] {
890 continueRewrites = false;
891
892 // Erase unreachable blocks
893 // Operations like:
894 // %add = arith.addi %add, %add : i64
895 // are legal in unreachable code. Unfortunately many patterns would be
896 // unsafe to apply on such IR and can lead to crashes or infinite
897 // loops.
898 continueRewrites |=
899 succeeded(eraseUnreachableBlocks(rewriter, region));
900
901 continueRewrites |= processWorklist();
902
903 // After applying patterns, make sure that the CFG of each of the
904 // regions is kept up to date.
905 if (config.getRegionSimplificationLevel() !=
907 continueRewrites |= succeeded(simplifyRegions(
908 rewriter, region,
909 /*mergeBlocks=*/config.getRegionSimplificationLevel() ==
911 }
912
913 // Optionally run full CSE. If CSE changes the IR we iterate again so
914 // that patterns can fire on the deduplicated operations.
915 if (config.isCSEBetweenIterationsEnabled()) {
916 DominanceInfo domInfo;
917 bool cseChanged = false;
918 eliminateCommonSubExpressions(rewriter, domInfo, region,
919 &cseChanged);
920 continueRewrites |= cseChanged;
921 }
922 },
923 {&region}, iteration);
924 } while (continueRewrites);
925
926 if (changed)
927 *changed = iteration > 1;
928
929 // Whether the rewrite converges, i.e. wasn't changed in the last iteration.
930 return success(!continueRewrites);
931}
932
933LogicalResult
935 const FrozenRewritePatternSet &patterns,
936 GreedyRewriteConfig config, bool *changed) {
937 // The top-level operation must be known to be isolated from above to
938 // prevent performing canonicalizations on operations defined at or above
939 // the region containing 'op'.
941 "patterns can only be applied to operations IsolatedFromAbove");
942
943 // Set scope if not specified.
944 if (!config.getScope())
945 config.setScope(&region);
946
947#if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
948 if (failed(verify(config.getScope()->getParentOp())))
949 llvm::report_fatal_error(
950 "greedy pattern rewriter input IR failed to verify");
951#endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
952
953 // Start the pattern driver.
954 RegionPatternRewriteDriver driver(region.getContext(), patterns, config,
955 region);
956 LogicalResult converged = std::move(driver).simplify(changed);
957 if (failed(converged))
958 LDBG() << "The pattern rewrite did not converge after scanning "
959 << config.getMaxIterations() << " times";
960 return converged;
961}
962
963//===----------------------------------------------------------------------===//
964// MultiOpPatternRewriteDriver
965//===----------------------------------------------------------------------===//
966
967namespace {
968/// This driver simplfies a list of ops.
969class MultiOpPatternRewriteDriver : public GreedyPatternRewriteDriver {
970public:
971 explicit MultiOpPatternRewriteDriver(
972 MLIRContext *ctx, const FrozenRewritePatternSet &patterns,
974 llvm::SmallDenseSet<Operation *, 4> *survivingOps = nullptr);
975
976 /// Simplify `ops`. Return `success` if the transformation converged.
977 LogicalResult simplify(ArrayRef<Operation *> ops, bool *changed = nullptr) &&;
978
979private:
980 void notifyOperationErased(Operation *op) override {
981 GreedyPatternRewriteDriver::notifyOperationErased(op);
982 if (survivingOps)
983 survivingOps->erase(op);
984 }
985
986 /// An optional set of ops that survived the rewrite. This set is populated
987 /// at the beginning of `simplifyLocally` with the inititally provided list
988 /// of ops.
989 llvm::SmallDenseSet<Operation *, 4> *const survivingOps = nullptr;
990};
991} // namespace
992
993MultiOpPatternRewriteDriver::MultiOpPatternRewriteDriver(
994 MLIRContext *ctx, const FrozenRewritePatternSet &patterns,
996 llvm::SmallDenseSet<Operation *, 4> *survivingOps)
997 : GreedyPatternRewriteDriver(ctx, patterns, config),
998 survivingOps(survivingOps) {
1000 strictModeFilteredOps.insert_range(ops);
1001
1002 if (survivingOps) {
1003 survivingOps->clear();
1004 survivingOps->insert_range(ops);
1005 }
1006}
1007
1008LogicalResult MultiOpPatternRewriteDriver::simplify(ArrayRef<Operation *> ops,
1009 bool *changed) && {
1010 // Populate the initial worklist.
1011 for (Operation *op : ops)
1012 addSingleOpToWorklist(op);
1013
1014 // Process ops on the worklist.
1015 bool result = processWorklist();
1016 if (changed)
1017 *changed = result;
1018
1019 return success(worklist.empty());
1020}
1021
1022/// Find the region that is the closest common ancestor of all given ops.
1023///
1024/// Note: This function returns `nullptr` if there is a top-level op among the
1025/// given list of ops.
1027 assert(!ops.empty() && "expected at least one op");
1028 // Fast path in case there is only one op.
1029 if (ops.size() == 1)
1030 return ops.front()->getParentRegion();
1031
1032 Region *region = ops.front()->getParentRegion();
1033 ops = ops.drop_front();
1034 int sz = ops.size();
1035 llvm::BitVector remainingOps(sz, true);
1036 while (region) {
1037 int pos = -1;
1038 // Iterate over all remaining ops.
1039 while ((pos = remainingOps.find_first_in(pos + 1, sz)) != -1) {
1040 // Is this op contained in `region`?
1041 if (region->findAncestorOpInRegion(*ops[pos]))
1042 remainingOps.reset(pos);
1043 }
1044 if (remainingOps.none())
1045 break;
1046 region = region->getParentRegion();
1047 }
1048 return region;
1049}
1050
1053 GreedyRewriteConfig config, bool *changed, bool *allErased) {
1054 if (ops.empty()) {
1055 if (changed)
1056 *changed = false;
1057 if (allErased)
1058 *allErased = true;
1059 return success();
1060 }
1061
1062 // Determine scope of rewrite.
1063 if (!config.getScope()) {
1064 // Compute scope if none was provided. The scope will remain `nullptr` if
1065 // there is a top-level op among `ops`.
1066 config.setScope(findCommonAncestor(ops));
1067 } else {
1068 // If a scope was provided, make sure that all ops are in scope.
1069#ifndef NDEBUG
1070 bool allOpsInScope = llvm::all_of(ops, [&](Operation *op) {
1071 return static_cast<bool>(config.getScope()->findAncestorOpInRegion(*op));
1072 });
1073 assert(allOpsInScope && "ops must be within the specified scope");
1074#endif // NDEBUG
1075 }
1076
1077#if MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
1078 if (config.getScope() && failed(verify(config.getScope()->getParentOp())))
1079 llvm::report_fatal_error(
1080 "greedy pattern rewriter input IR failed to verify");
1081#endif // MLIR_ENABLE_EXPENSIVE_PATTERN_API_CHECKS
1082
1083 // Start the pattern driver.
1084 llvm::SmallDenseSet<Operation *, 4> surviving;
1085 MultiOpPatternRewriteDriver driver(ops.front()->getContext(), patterns,
1086 config, ops,
1087 allErased ? &surviving : nullptr);
1088 LogicalResult converged = std::move(driver).simplify(ops, changed);
1089 if (allErased)
1090 *allErased = surviving.empty();
1091 if (failed(converged))
1092 LDBG() << "The pattern rewrite did not converge after "
1093 << config.getMaxNumRewrites() << " rewrites";
1094 return converged;
1095}
return success()
static Region * findCommonAncestor(ArrayRef< Operation * > ops)
Find the region that is the closest common ancestor of all given ops.
#define DEBUG_TYPE
if(!isCopyOut)
*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
static std::string diag(const llvm::Value &value)
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:33
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.
A class for computing basic dominance information.
Definition Dominance.h:140
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:63
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:329
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:400
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.
A wrapper class that allows for printing an operation with a set of flags, useful to act as a "stream...
Definition Operation.h:1143
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
Operation is the basic unit of execution within MLIR.
Definition Operation.h:88
bool hasTrait()
Returns true if the operation was registered with a particular trait, e.g.
Definition Operation.h:775
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:433
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition Operation.h:252
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:116
result_type_range getResultTypes()
Definition Operation.h:454
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:404
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:823
Region * getParentRegion()
Returns the region to which the instruction belongs.
Definition Operation.h:248
void erase()
Remove this operation from its parent block and delete it.
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
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:200
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:296
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
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:418
@ 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...
LogicalResult eraseUnreachableBlocks(RewriterBase &rewriter, MutableArrayRef< Region > regions)
Erase the unreachable blocks within the provided regions.
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:120
@ 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:480
llvm::function_ref< Fn > function_ref
Definition LLVM.h:147
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.