土法炼钢兴趣小组的算法知识备份

【编译器与 MLIR】Pass 管理与分析

文章导航

分类入口
compilerarchitecture
标签入口
#mlir#llvm#compiler#pass#pipeline#optimization#analysis

目录

Pass 管理与分析

前面的第二部分覆盖了 IR 的数据结构。从这一篇开始进入变换层——如何写 Pass 来分析和变换 IR。

MLIR 的 Pass 基础设施复用了 LLVM 的成熟经验(PassManager、AnalysisManager、PassInstrumentation),但针对多方言和 Region 嵌套做了关键扩展。

一、MLIR Pass 与 LLVM Pass 的模型差异

从 LLVM Pass 转到 MLIR Pass,核心差异有三个:

1. 没有 FunctionPass / ModulePass 的多级继承

LLVM 有 FunctionPassModulePassLoopPass 等按作用域分级的 Pass 类型。MLIR 统一使用 OperationPass<OpT>——你只需要声明 Pass 操作的 Op 类型。mlir-opt 在执行时会自动遍历 IR 找到所有目标 Op 并依次运行 Pass:

这种统一性来自 MLIR 的”一切皆 Op”设计。循环体是 Op、函数是 Op、Module 也是 Op——不存在需要特殊分类的 IR 单元。

2. Pass 是局部作用域的

OperationPass<arith::AddIOp> 只能访问和修改 arith::AddIOp 的 Region 内部的 IR。它看不到 sibling Op 或外层 Module。这种局部性既是限制也是保证——它使得 Pass 的并行执行成为可能,且 Pass 之间的耦合最小。

3. AnalysisManager 是嵌套的

每个 Pass 运行时获得一个 AnalysisManager,它与 Pass 的作用域对应。对 func::FuncOp 的 Pass 分析函数内部的数据流;对 ModuleOp 的 Pass 做跨函数分析。

二、编写一个 OperationPass

以一个简单的 Pass 为例——统计 arith::AddIOp 的数量:

// include/MyPass/CountAddOps.h
#include "mlir/Pass/Pass.h"

namespace mlir {
namespace my {

class CountAddOpsPass : public PassWrapper<CountAddOpsPass,
                                            OperationPass<func::FuncOp>> {
public:
  MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(CountAddOpsPass)

  void runOnOperation() override;

  // Pass 选项(可通过命令行传递)
  // Option<bool> verbose{*this, "verbose",
  //   llvm::cl::desc("Enable verbose output"), llvm::cl::init(false)};
};

} // namespace my
} // namespace mlir
// lib/MyPass/CountAddOps.cpp
#include "CountAddOps.h"

void CountAddOpsPass::runOnOperation() {
  func::FuncOp func = getOperation();  // 获取当前 Pass 作用的 Op
  int count = 0;

  func.walk([&](arith::AddIOp addOp) {
    count++;
  });

  llvm::outs() << "Function '" << func.getName()
               << "' has " << count << " arith.addi ops\n";
}

// 注册 Pass
std::unique_ptr<Pass> mlir::my::createCountAddOpsPass() {
  return std::make_unique<CountAddOpsPass>();
}

关键点:

2.1 注册 Pass 到命令行

// 在 Pass 注册文件中
namespace {
// 将 Pass 注册到 mlir-opt 的命令行
struct CountAddOpsPassRegistration
    : public PassRegistration<CountAddOpsPass> {
  // Pass 的文本参数:mlir-opt --count-add-ops
  // 参数名会自动从 CamelCase 转为 kebab-case
};
} // namespace

注册后即可通过命令行运行:

mlir-opt input.mlir --count-add-ops

三、Pass 的依赖管理与流水线

3.1 分析依赖

一个 Pass 可以声明它依赖某个分析(Analysis)。MLIR 的 AnalysisManager 保证:

  1. 当 Pass 请求一个分析时,如果尚未计算则创建并缓存它;
  2. 如果一个 Pass 修改了 IR(通过 markAnalysesPreserved 告知),缓存的分析被正确失效。
// 声明依赖的分析
void CountAddOpsPass::runOnOperation() {
  // 请求分析——如果之前被计算过且仍然有效,返回缓存的
  auto &domInfo = getAnalysis<DominanceInfo>();
  // 使用 domInfo ...
}

// 告知哪些分析不会被此 Pass 破坏
// (默认情况下,修改 IR 的 Pass 会使所有分析失效)
void runOnOperation() override {
  // 做不可变的操作——不需要修改任何分析
  markAllAnalysesPreserved();    // 保留所有分析
  // 或
  // markAnalysesPreserved<DominanceInfo>();  // 只保留特定分析
}

3.2 Pass 流水线

mlir-opt 允许串联多个 Pass:

mlir-opt input.mlir \
  --canonicalize \
  --cse \
  --convert-scf-to-cf \
  --convert-cf-to-llvm \
  --convert-func-to-llvm

PassManager 也可以在 C++ 中程序化构建:

PassManager pm(&ctx);
pm.addPass(createCanonicalizerPass());
pm.addPass(createCSEPass());
pm.addNestedPass<func::FuncOp>(createConvertSCFToCFPass());

if (failed(pm.run(moduleOp))) {
  // 处理失败
}

OpPassManager 支持嵌套——你可以指定一个 Pass 只对特定类型的 Op 内部的 Region 运行:

OpPassManager &funcPM = pm.nest<func::FuncOp>();
funcPM.addPass(createMyFunctionLevelPass());

3.3 多线程 Pass 执行

MLIR 的 PassManager 支持多线程执行。对于 OperationPass<func::FuncOp>,如果 IR 中有多个独立的 func.func,它们可以在不同线程上并行运行——因为每个 Pass 实例只能看到自己的 func::FuncOp

PassManager pm(&ctx);
pm.enableMultithreading();   // 启用多线程
// 默认由环境变量或硬件并发度决定线程数
// 也可手动指定:pm.getContext()->setNumThreads(4);

多线程的安全性由 OperationPass 的作用域隔离保证——不依赖全局锁。

四、Pass 的 IR 修改通知与 Analysis 保全

当一个 Pass 修改 IR 时,它需要告诉框架哪些修改是”安全的”——即哪些分析仍然有效:

void runOnOperation() override {
  // 移除了一个操作
  op->erase();

  // 告诉框架:支配树和循环信息已经失效
  // 但其他分析可以保留
  markAnalysesPreserved<CallGraph>();  // CallGraph 未被影响

  // 如果没有显式标记,框架假设所有分析都已失效
}

这个机制在 LLVM 中也存在但在 MLIR 中更为敏感——因为 MLIR 的 Pass 可以嵌套运行,一个内层 Pass 修改了 scf.for 的内部 Region,不确定是否影响外层的分析。

五、调试 Pass 流水线

Pass 调试的常用 mlir-opt 标志(--mlir-print-ir-before-all--mlir-print-ir-after-change--mlir-timing--crash-reproducer 等)在 第 16 章 有完整操作指南和诊断流程表。本节只保留与 Pass 机制直接相关的两点:

六、Pass 的注册与发现

在 CMake 中注册 Pass 库:

add_mlir_library(MLIRMyPass
  CountAddOps.cpp

  LINK_LIBS PUBLIC
  MLIRPass
  MLIRFuncDialect
  MLIRArithDialect
)

这样 mlir-opt 在链接到这个库后,就能自动发现并注册 --count-add-ops 选项。

七、本篇后续

Pass 管理是编译流水线的骨架。下一章进模式重写(Pattern Rewrite)——Pass 内部最重要的变换机制:如何匹配 IR 模式并替换为等价的更优表示。

参考资料

官方文档(A 级)

源码(A 级)

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-06-09 · compiler / architecture

【编译器与 MLIR】模式重写与规范化框架

深入 MLIR 的模式重写引擎:RewritePattern 的驱动模型、matchAndRewrite 的匹配与替换协议、规范化(Canonicalization)规则的编写、折叠与代数简化的最佳实践,以及 GreedyPatternRewriteDriver 的工作机制。


By .