必须用 MLIR 解析器加载 .mlir 文件为 ModuleOp 树后修改,不可直接文本编辑;需注册方言、用 parseSourceFile 安全加载,修改前校验 op 合法性,保存前调用 verify() 并优先使用 writeBytecodeToFile 保留调试信息。

直接读取并修改 .mlir 文件不能靠文本编辑器“改完保存”了事——MLIR 是结构化 IR,必须用 MLIR 的解析器加载成内存中的 ModuleOp 树,再通过重写器(PatternRewriter)或手动操作 API 修改节点,否则会破坏 SSA 约束、类型一致性或位置信息(loc),导致后续 pass 崩溃或生成错误代码。
用 parseSourceFile 加载 .mlir 文件到 ModuleOp
这是最安全的起点。不要用 llvm::MemoryBuffer::getFile + 手动 parse;MLIR 提供了封装好的接口,自动处理 dialect 注册、上下文生命周期和错误报告:
-
mlir::OwningOpRef<:moduleop></:moduleop>是推荐的持有方式,避免裸指针泄漏 - 必须提前注册该文件中用到的所有方言(如
func、arith、toy),否则解析时会报unknown dialect - 路径必须是绝对路径或相对于当前工作目录,相对路径在不同构建目录下容易出错
示例片段:
mlir::MLIRContext context;
context.loadDialect<mlir::func::FuncDialect, mlir::arith::ArithDialect>();
auto module = mlir::parseSourceFile<mlir::ModuleOp>(inputPath, &context);
if (!module) {
llvm::errs() << "Failed to parse " << inputPath << "\n";
return 1;
}
修改操作前先确认 dialect 和 op 合法性
直接调用 op->erase() 或 rewriter.replaceOp() 前,务必检查:op->getName().getDialectNamespace() 是否是你期望的方言(比如 "toy" 而不是 "arith"),以及 op->hasTrait<mlir::OpTrait::IsTerminator>() 是否为 true——终结符不能随便删。
- 对
toy.constant这类带 dense attribute 的操作,修改值要用op->setAttr("value", newAttr),而不是改 operand(它没 operand) - 想给
func.func添加新参数?不能直接 push 到getFunctionType().getInputs()——那是只读视图,得用funcOp.insertArgument()并更新 function type - 跨 block 移动操作?先用
rewriter.setInsertionPointAfter(op),再rewriter.moveOpBefore(),否则报invalid insertion point
保存修改后模块必须用 writeBytecodeToFile 或 print
直接 llvm::raw_fd_ostream 写 module->print() 输出是可读的文本格式,但会丢失原始 source loc(除非构造时传 -mlir-print-debuginfo);若要保留调试信息或用于后续 JIT,必须用 mlir::writeBytecodeToFile() 生成二进制 MLIR(.mlirbc)。
- 文本输出默认不带换行缩进,加
mlir::OpPrintingFlags().useLocalScope().enableDebugInfo()可提升可读性 - bytecode 格式不可编辑,但体积小、加载快,且能被
mlir-opt直接消费 - 修改后未调用
module->verify()就保存,可能把非法 IR 写入磁盘——建议在保存前加一次校验
真正难的不是“怎么改”,而是“改完是否仍满足方言约束”:比如 toy 方言要求 toy.print 必须是函数末尾唯一 terminator,你往中间插个 toy.constant 后忘了调整 terminator 位置,整个模块就 invalid 了。这类隐含规则不会报编译错误,只会在下游 pass 里静默失败或 crash。

















