RewritePattern and canonicalization
A RewritePattern with a greedy driver rewrites ops; canonicalization runs fold hooks that cannot create ops and does less than a pattern.
A pattern is a local rewrite
A RewritePattern matches one root operation and replaces it. Every change goes through the PatternRewriter, because the driver tracks erasures and replacements:
struct ExtractSplat : OpRewritePattern<tensor::ExtractOp> {
using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(tensor::ExtractOp extract,
PatternRewriter &rewriter) const override {
auto splat = extract.getTensor().getDefiningOp<tensor::SplatOp>();
if (splat) {
rewriter.replaceOp(extract, splat.getInput());
return success();
}
return rewriter.notifyMatchFailure(extract, "not a splat");
}
};
notifyMatchFailure is not decoration: under -debug-only=greedy-rewriter it is the only record of why a pattern declined. replaceOp needs exactly one replacement value per result.
The driver
A RewritePatternSet holds the patterns and a driver applies them to a fixed point. Releases differ on the driver’s name: older trees spell it applyPatternsAndFoldGreedily(op, std::move(patterns)), newer ones applyPatternsGreedily, so confirm the spelling in GreedyPatternRewriteDriver.h. GreedyRewriteConfig caps the iteration count and selects top-down or bottom-up traversal. Patterns are tried in PatternBenefit order, highest first.
Because the driver revisits operations after rewrites elsewhere in the region, a pattern must not hold a pointer to a second operation across a rewrite. Re-query it from the operand each time.
Canonicalization is the fixed half
-canonicalize runs the Canonicalizer pass, which applies every loaded dialect’s canonicalization patterns plus each op’s fold hook until a fixed point. fold is weaker than a pattern by design: it returns existing values or attributes, or mutates the op in place, and it cannot create operations. In ODS, hasFolder, hasCanonicalizer, and hasCanonicalizeMethod choose which hooks are generated. Canonicalization is best-effort: a fixed point is not a normal form.
Things that break
Two patterns that undo each other never converge. The driver runs to its iteration cap and reports that the IR did not converge; a pattern that returns success() without changing anything burns the same budget and looks identical. Both produce an error from the pass rather than wrong output, but only if you run the driver. Testing one pattern at a time in isolation is how non-convergence reaches main.
Sources
- Pattern Rewriting: generic DAG-to-DAG rewritingdocs · LLVM 19
- Operation Canonicalizationdocs · LLVM 19
- Operation Definition Specification (ODS)docs
A version records the documentation the claim was checked against, not a house pin. Confirm flags with --help on the release you installed.
Check yourself
Pattern A rewrites `x` to `y`, and a canonicalization pattern rewrites `y` back to `x`. What happens under the greedy driver?
Why can a `fold` hook not create a new operation?
What does `mlir-opt --canonicalize` apply?