mlir-reduce
是 MLIR 生态系统中的一个工具,主要用于最小化 MLIR IR(中间表示)测试案例,帮助开发者调试和分析编译器问题或优化问题。类似于编译器中的bugpoint
,它通过自动简化问题来生成更小的测试用例,便于问题的定位和修复
工作原理使用方法1. 创建Test脚本2. 写入执行命令3. 使用mlir-reducemlir-reducer工具二次开发1. 工具实现2. Pattern实现mlir-reduce代码走读1. lib/Reducer2. lib/Tools/mlir-reducer3. Tools/mlir-reducer
工作原理
mlir-reduce
的核心原理是使用一种"delta debugging"(增量调试)的技术。它会不断尝试去除不必要的操作、块、或模块,并测试剩余的代码是否仍然能触发目标行为(如崩溃、性能问题、错误等)。具体步骤如下:- 输入测试用例:你提供一个可能导致问题的 MLIR 输入文件。
- 指定测试条件:提供一个测试脚本或命令,它会返回成功或失败,来判断简化后的 IR 是否依然能重现问题。
- 递归简化:
mlir-reduce
逐步简化这个输入文件,不断删除不必要的操作和模块。
- 输出最小化测试用例:最终生成一个最小的测试用例文件,该文件仍然会触发错误或表现出原有的问题。
使用方法
1. 创建Test脚本
- 创建脚本文件,给予执行权限
touch run.sh chmod +x run.sh
2. 写入执行命令
执行命令就是能复现错误的方式,如果返回值是期望的(错误代码), 脚本返回1, 否则返回0,表示不感兴趣
#!/bin/bash mlir-opt -convert-vector-to-spirv $1 | grep "failed to materialize" if [[ $? -eq 1 ]]; then exit 1 else exit 0 fi
3. 使用mlir-reduce
- 替换Input和TEST_SCRIPT路径为实际路径
mlir-reduce $INPUT -reduction-tree='traversal-mode=0 test=$TEST_SCRIPT'
mlir-reducer工具二次开发
一般项目中会定义自己的Dialect,需要对该工具进行二次开发,引入针对于自己Dialect的相关pattern进行IR缩减,这一节主要介绍如何基于官方的reducer开发一个自己的reducer
1. 工具实现
在工程的Tools目录下新建一个xxx-reducer的文件夹,并构造相应的文件
. ├── CMakeLists.txt └── reducer.cpp
- CMakeLists.txt可以直接照抄官方的,做一点简单的修改即可
- cpp文件里面对自己的Dialect进行注册
2. Pattern实现
reducer会调用一些相关的缩减pattern进行IR缩减, 这一部分需要自己实现或者使用td文件自动定义
使用DialectInterface对相关pattern进行注册:
#include "mlir/Reducer/ReductionPatternInterface.h" // populateWithGenerated是td文件自己生成的函数,对td生成的pattern进行注册 void populateMyReductionPatterns(RewritePatternSet &patterns) { populateWithGenerated(patterns); } // 该接口进行Pattern的注册 struct MyReductionPatternInterface : public DialectReductionPatternInterface { virtual void populateReductionPatterns(RewritePatternSet &patterns) const final { populateMyReductionPatterns(patterns); } } // 调用Dialect的registerInterfaces对接口进行注册, 该函数需要在td的Dialect定义里面进行定义 void YourDialect::registerInterfaces() { addInterface<MyReductionPatternInterface>(); }
实现完成之后,reducer会自动的调用这里注册的pattern对IR进行缩减
mlir-reduce代码走读
1. lib/Reducer
实现缩减树算法
. ├── CMakeLists.txt ├── OptReductionPass.cpp ├── ReductionNode.cpp ├── ReductionTreePass.cpp └── Tester.cpp
- OptReductionPass.cpp
- 初始化一个passManager, 添加缩减相关的Pass
- 检查初始状态是否满足条件, 初始必须是interested的
- runPipeLine,得到缩减后的module
Reduction lib的入口函数,实现了一个名为OptReductionPass的Pass,然后使用缩减树对原始IR进行缩减,得到最小的能够复现错误的子图
- ReductionNode.cpp
ReductionNode::ReductionNode()
ReductionNode::initialize(ModuleOp parentModule, Region &targetRegion)
ReductionNode::generateNewVariants()
- 如果尚未生成任何变体,删除一个范围来创建新变体。
- 如果已经生成过变体,寻找最大范围,将其分成两部分,并基于此生成两个新变体。
ReductionNode::update(std::pair<Tester::Interestingness, size_t> result)
- 根据给定的测试结果,更新有趣性(interestingness)和大小(size)。
- 如果节点被认为是有趣的,重置范围;如果不有趣,释放内存以节省资源。
ReductionNode::iterator<SinglePath>::getNeighbors(ReductionNode *node)
- 使用 "Single Path" 策略,遍历最小且有趣的变体,直到无法生成新的成功变体。
- 如果当前节点的所有变体都未测试,返回空;否则,继续遍历最小变体或生成新变体。
缩减树的节点实现
构造函数,初始化
ReductionNode
。如果是根节点,parentNode
指向自身,并调用 initialize()
来克隆父模块并映射区域。初始化
ReductionNode
,通过 IRMapping
克隆模块,并确保目标区域与克隆后的模块相对应。生成新的简化变体(variants)。
更新当前节点的状态。
获取节点的邻居节点。
- ReductionTreePass.cpp 该文件实现了 ReductionTreePass,它为不同的减少(简化)操作提供了一个框架,并允许用户自定义生成变体的行为。这个 pass 构建了一个简化树结构,该结构用于在简化过程中跟踪生成的不同简化变体。主要功能包括遍历不同的简化树路径,以找到最优(最小化)的变体,并通过用户定义的测试器(Tester)来验证变体的有效性。
ReductionTreePass::runOnOperation()
- 流程:
- 获取顶层
Operation
。 - 构建
workList
(工作列表),初始化为当前Operation
。 - 循环处理工作列表中的每个操作:
- 对每个操作中的所有
Region
调用reduceOp
进行简化。 - 对有嵌套区域的操作,将其放入
workList
继续处理。 - 作用: 运行这个 pass,将对操作中的
Region
进行简化遍历,处理所有包含子区域的操作。 ReductionTreePass::reduceOp(ModuleOp module, Region ®ion)
- 流程:
- 创建
Tester
对象来测试模块是否有趣(interesting)。 - 根据不同的遍历模式(如
SinglePath
),调用相应的简化方法findOptimal
。 - 作用: 对给定的
Region
应用简化策略并测试简化后的有趣性,生成最小化的变体。 findOptimal
(两个重载版本)- 流程:
- 第一步: 在
eraseOpNotInRange
设置为true
的情况下,仅保留有趣的操作并删除其他操作。 - 第二步: 在
eraseOpNotInRange
设置为false
时,应用简化模式将操作转化为更简单的形式。 - 作用: 通过两步过程实现模块的简化,先选择有趣的操作范围,再进行操作简化。
applyPatterns
- 流程:
- 遍历区域内的操作,按索引范围筛选需要保留的操作。
- 对符合条件的操作应用简化模式。
- 如果设置了
eraseOpNotInRange
,则删除未在范围内的操作。 - 作用: 根据
ReductionNode
的范围,对操作进行简化或删除非必要的操作。
2. lib/Tools/mlir-reducer
mlir-reduce.cpp
实现了 MLIR reducer 工具的主要框架,该工具用于简化 MLIR 测试用例。它从命令行读取输入 MLIR 文件,通过一系列的简化 pass 操作,输出最简化后的变体。其核心功能是通过提供简化框架,帮助用户减少复杂的 MLIR 测试用例,使其更容易调试和分析- 命令行参数解析:
- 使用
llvm::cl::opt
定义输入文件、输出文件以及是否插入隐式module
操作等命令行选项。 - 注册和隐藏不相关选项,确保用户只看到与
mlir-reduce
工具相关的参数。
- 文件加载与解析:
- 通过
loadModule()
函数解析输入的 MLIR 文件。如果文件解析失败,工具会直接返回错误。
- Pass 管线设置:
- 创建
PassManager
来管理和执行不同的简化 pass。 - 使用
PassPipelineCLParser
解析从命令行指定的 pass 管线,并将这些 pass 添加到PassManager
中。
- 运行 Pass 管线:
- 对克隆的 MLIR 操作(Op)执行简化 pass 管线。
- 简化后的 MLIR 操作输出到用户指定的文件。
3. Tools/mlir-reducer
主要用于Dialect register,然后生成基于给定Dialect的bin文件,核心是调用lib里面的mlir-reducer