fold优先于canonicalization触发,二者协同实现“常量传播+结构简化”闭环:fold在ir构建、预处理及显式调用时轻量计算或消冗,canonicalize则通过重写模式进行跨op结构优化。

fold 优先于 canonicalization 触发,但二者协同完成“常量传播 + 结构简化”闭环
fold 是什么,它在什么时机被调用
fold 是 Operation 自带的轻量级化简能力,用于在不改变语义前提下直接计算出结果(如 addi %c0, %c1 → %c1),或提前消去冗余操作(如 mul %x, %c0 → %c0)。它不依赖 Pattern Rewriter,也不修改 IR 结构,只返回新值或空列表。
常见触发点包括:
- IR 构建时(如
builder.create<addiop>(...)</addiop>内部会自动fold) - Canonicalization Pass 执行前的预处理阶段
- 某些转换 Pass(如 Dialect Conversion)中显式调用
op.fold(...)
canonicalization 做什么,为什么不能只靠 fold
canonicalize 是更重、更结构化的优化:它通过注册 getCanonicalizationPatterns 返回一组 RewritePattern,匹配并替换整个子图(DAG)。比如 transpose(transpose(%x)) → %x,这种跨多个 Op 的等价替换无法由单个 fold 完成。
关键差异:
-
fold只能处理当前 Op 及其 immediate operands;canonicalize可访问 users、nested regions、甚至跨 dialect 的 operand 依赖 -
fold返回的是值(Value或空),canonicalize返回的是重写指令(PatternRewriter::replaceOp等) - 一个 Op 的
fold成功后,其 users 不会自动重试fold;而canonicalizePass 会迭代运行直到无变化
两者配合的真实工作流
典型优化链路是:fold 先把局部常量暴露出来 → canonicalize 利用这些常量触发更大范围的 DAG 替换。例如:
%0 = arith.constant 0 : i32 %1 = arith.addi %x, %0 : i32 // fold 返回 [%x],直接替换为 %x %2 = arith.muli %1, %y : i32 // 现在变成 muli %x, %y —— 更容易被 canonicalize 中的“乘法单位元” pattern 匹配
实际开发中要注意:
- 不要在
fold里做 heavy computation(如遍历 users 或创建新 op),否则拖慢 IR 构建 -
canonicalizepattern 的matchAndRewrite中,应先尝试op.fold(...)获取候选值,再决定是否重写,避免重复逻辑 - 若某个 Op 同时有
fold和canonicalize实现,fold必须保证返回结果与canonicalize的最终效果一致,否则会出现 pass 迭代不收敛
容易被忽略的坑:fold 返回值的生命周期和 ownership
fold 返回的 Value 必须来自已有 IR(如 operand、constant、block argument),不能 new 一个临时 Value。否则会在后续 canonicalize 或验证中触发 use-def 链断裂或 verifier 失败。
典型错误写法:
// ❌ 错误:创建了未插入 block 的新 Value auto *newOp = rewriter.create<:constantop>(...); return std::make_pair(newOp->getResult(0), nullptr); // ✅ 正确:复用已有 operand 或 constant if (auto c = getOperand(0).getDefiningOp<:constantop>()) return std::make_pair(c.getResult(), nullptr); </:constantop></:constantop>
真正难调试的问题往往出在这里:fold 看似成功,但返回的 Value 没有合法 parent,导致后续 canonicalize 在 match 时 operand 不满足约束,静默跳过。











