5. MLIR相关语法

系列文章目录

章节1:什么是MLIR

章节2:MLIR的引入

章节3:MLIR完成一个端到端的流程

章节4:编写一个pass

章节5:MLIR相关语法

目录

系列文章目录

一、TableGen中的Trait

核心区别对比

[1. 使用方式:混入 vs 实例化](#1. 使用方式:混入 vs 实例化)

[2. 生命周期:编译期 vs 运行时](#2. 生命周期:编译期 vs 运行时)

[3. 存在形式:必须依附于 ConcreteType](#3. 存在形式:必须依附于 ConcreteType)

[4. 方法调用:框架调度 vs 显式调用](#4. 方法调用:框架调度 vs 显式调用)

[5. 设计目的:声明 + 注入 vs 封装](#5. 设计目的:声明 + 注入 vs 封装)

[6. 组合性:可叠加](#6. 组合性:可叠加)

一个类比

一句话总结

二、TableGen中def的作用

[1. def 的基本作用](#1. def 的基本作用)

[2. 在 MLIR ODS 中,def 具体定义什么?](#2. 在 MLIR ODS 中,def 具体定义什么?)

三、operation类和op类

[核心区别:运行时容器 vs. 编译期包装](#核心区别:运行时容器 vs. 编译期包装)

它们如何协同工作?

一个直观的例子

总结

总结



一、TableGen中的Trait

从 C++ 语法层面看,MLIR 的 Trait 就是一个类模板 ,和普通类没有本质区别------它也能有成员变量、成员函数、静态函数、继承关系。但从设计意图和使用范式上看,它和普通类有几点关键区别。

📊 核心区别对比

维度 普通类 MLIR Trait
使用方式 实例化对象,通过对象调用 作为基类混入到 Op/Attribute/Type 中
生命周期 运行时存在对象实例 编译期生效,不独立实例化
存在形式 可以独立存在 必须依附 于某个 ConcreteType
方法调用 显式调用 成员方法被宿主继承;静态钩子由框架自动调度
设计目的 封装数据和行为 声明约束 + 注入通用实现
组合性 单继承为主 一个 Op 可叠加多个 Trait

1. 使用方式:混入 vs 实例化

普通类你会 new 一个对象或用栈对象:

cpp

复制代码
MyClass obj;          // 实例化
obj.doSomething();    // 通过对象调用

Trait 从不被单独实例化。它的用法是作为模板参数"混入"到宿主类型里:

cpp

复制代码
class MyOp : public Op<MyOp, MyTrait> { ... };
// 这里没有 MyTrait 的对象,只有 MyOp 的对象

MyTrait 的代码通过继承成为 MyOp 的一部分,但你在运行时拿不到一个"MyTrait 实例"。

2. 生命周期:编译期 vs 运行时

普通类的方法在运行时通过对象调用,可能有虚函数动态分发。

Trait 的成员方法在编译期 就通过继承成为宿主的一部分,调用是静态解析 的,没有虚表开销。Trait 的静态钩子(verifyTrait、foldTrait)也是框架在编译期就知道该调用哪个,属于静态多态。

3. 存在形式:必须依附于 ConcreteType

普通类可以独立存在。Trait 的模板参数 ConcreteType 意味着它必须知道"自己附着在谁身上" 。一个 MyTrait 不能脱离具体的 Op 类型而存在------它存在的意义就是"给某个 ConcreteType 添加能力或约束"。

这也是为什么 Trait 用 CRTP :基类 TraitBase 需要知道派生 Trait 和宿主类型,才能在编译期完成静态调度。

4. 方法调用:框架调度 vs 显式调用

普通类的方法,你不调用它就不会执行。

Trait 的静态钩子 是框架按约定自动调用的:

  • verifyTrait:MLIR 验证 IR 时自动调用

  • foldTrait:常量折叠时作为回退自动调用

  • printTrait / parseTrait:自定义打印/解析时自动调用

你只要把 Trait 挂上去,这些逻辑就在合适的时机自动生效,不需要使用者手动触发。这是普通类没有的"框架回调"机制。

5. 设计目的:声明 + 注入 vs 封装

普通类的核心目的是封装数据和操作。Trait 的核心目的是:

  • 声明 :告诉 MLIR 世界"这个 Op 具备什么属性"(如 Commutative、Terminator)

  • 注入:把通用逻辑(验证、折叠、打印)一次性写好,自动赋给所有使用者

它更像 C++ 里的 mixin 或 C++20 的 concept,而不是传统意义上的"对象类"。

6. 组合性:可叠加

一个 Op 可以同时附加多个 Trait:

cpp

复制代码
class MyOp : public Op<MyOp, TraitA, TraitB, TraitC> { ... };

每个 Trait 各自注入自己的方法和钩子,互不冲突(同名方法需要宿主自己覆盖解决)。普通类单继承下很难做到这种灵活的横向能力组合。


一个类比

概念 类比
普通类 一个独立的产品,有完整的数据和行为
Trait 一张能力标签/插件,贴到产品上就赋予它额外能力和约束

Trait 不是"另一种类",而是一种用类模板实现的、面向框架回调的、编译期混入机制。语法上它是类,语义上它是"给宿主类型打的标签 + 附赠的代码"。

一句话总结

普通类回答"我是什么、我能做什么";Trait 回答"宿主具备什么属性、框架该在什么时候调用我注入的逻辑"。 前者是独立对象,后者是依附于宿主的编译期能力模块。

二、TableGen中def的作用

ef 是 TableGen 用来"定义一条具体记录(record)"的关键字 。它本身不是 C++ 的 def,也不是运行时执行的东西;它的作用是在 .td 文件里声明一个具体实体 ,然后由 mlir-tblgen 解析并生成对应的 C++ 代码。

1. def 的基本作用

TableGen 里常见两种定义:

tablegen

复制代码
class MyClass<...> { ... }   // 定义“模板/抽象记录”
def MyRecord : MyClass<...>; // 定义“具体记录”
  • class:定义可复用的模板,通常带参数,不直接生成最终实体。

  • def :基于某个 class 或基类,定义一个具体的、有名字的记录。

例如:

tablegen

复制代码
def MyOp : Op<"my.op", [Pure]> {
  let summary = "my op";
  let arguments = (ins I32:$input);
  let results = (outs I32:$output);
}

这里 def MyOp 就是在 ODS 中定义了一个名为 MyOp 的具体操作。

2. 在 MLIR ODS 中,def 具体定义什么?

def 可以定义 MLIR 中的多种实体:

def 定义的对象 例子 作用
Operation def AddOp : Op<"arith.addi", ...> 定义一个具体算子
Attribute def MyAttr : AttrDef<...> 定义一个自定义属性
Type def MyType : TypeDef<...> 定义一个自定义类型
Pattern def MyPattern : Pat<...> 定义一个重写模式
Interface def MyInterface : OpInterface<...> 定义一个操作接口
Dialect def MyDialect : Dialect 定义一个方言

def 在 MLIR/TableGen 中的作用是:定义一个具体的记录(Operation、Attribute、Type、Pattern 等),给它一个唯一名字,并描述它的结构;TableGen 后端再根据这些 def 生成 C++ 代码。 它本身不执行任何逻辑,而是声明式地描述"有什么",把重复的 C++ 样板代码交给工具生成。

三、operation类和op类

Operation 是一个类(class);Op 是一个类模板(class template),本身不能直接实例化,必须提供模板实参,它本身几乎不持有数据,只是一个包装 Operation* 的强类型外壳,具体的操作类,比如 AddOp、ConstantOp,是通过继承 Op<AddOp, ...> 实例化出来的类。

在MLIR中,Operation 和 Op 是两层不同的抽象 ,服务于不同的场景。简单说,Operation 是运行时那个"具体的东西" ,而 Op 是你在C++代码里用来方便、安全地操作它的"类型化包装"。

核心区别:运行时容器 vs. 编译期包装

维度 mlir::Operation mlir::Op<...> (及各派生类)
角色 运行时通用容器,代表IR中一个实际存在的操作指令 编译期类型安全包装 ,是访问Operation的"智能指针"
类型信息 类型擦除,不知道自己是"加法"还是"常量",只持有操作名(字符串)等通用信息 强类型 ,AddOp、ConstantOp等具体类明确知道自己是什么操作
核心职责 存储所有操作共有的数据:操作数、结果、属性、位置、所属区块等 提供操作特定的、语义化的API ,如 getLhs()、getRhs() 等便捷访问器
使用方式 在Pass中遍历时,通常以 Operation* 指针形式出现 按值 传递和持有,内部仅包含一个 Operation* 指针,非常轻量

它们如何协同工作?

Operation 是数据的"真身",Op 是指向它的"遥控器"。

  • Operation 持有全部数据 :所有操作数、结果、属性等实际数据,都存储在 Operation 对象内部。Op 派生类自身通常不包含任何数据成员 ,它只是一个空壳,所有数据都委托给它所包装的那个 Operation 实例。

  • Op 是轻量包装 :mlir::Op<ConcreteType, Traits...> 是一个模板类,它在编译期通过 ConcreteType(如 AddOp)和 Traits 获得类型信息,从而提供类型安全的访问方法。它的本质就是一个包装了 Operation* 的强类型智能指针。

一个直观的例子

在MLIR的Toy教程中,ConstantOp 代表一个无输入、单输出的常量操作。它本身不存储那个常量值,值存储在它所包装的 Operation 对象里。ConstantOp 的职责是提供 getValue() 这类类型安全的方法,让你方便地取出那个值。

当你从通用的 Operation* 得到特定的 Op 时,用的是 dyn_cast ;反之,从 Op 取回底层的 Operation* 则用 getOperation():

cpp

复制代码
void process(mlir::Operation *operation) {
  // 从通用 Operation* 转换为类型安全的 ConstantOp
  ConstantOp op = llvm::dyn_cast<ConstantOp>(operation); 
  if (!op) return; // 如果不是 ConstantOp,转换失败

  // 从 Op 取回底层的 Operation*
  mlir::Operation *internal = op.getOperation(); 
  // internal == operation 为 true
}

总结

Operation 回答"IR里实际存在什么",是通用的、类型擦除的运行时实体;Op 回答"我该如何方便、安全地操作它",是具体的、类型安全的编译期包装。 在Pass中处理未知操作时用 Operation*,需要调用特定操作的语义化方法时,就 dyn_cast 到对应的 Op 类


总结

相关推荐
Rain的Java大神之路42 分钟前
面试官让你把 LLM、RAG、Agent、MCP 串起来讲清楚,90% 的 Java 工程师当场露馅
java·后端·面试
齐 飞1 小时前
VUE2常用语法总结
javascript·vue.js
无所谓^_^1 小时前
个人健康管理系统 | Springboot3 | Vue3
java·vue·毕业设计·springboot·课程设计·毕设·前后端分离
恋猫de小郭1 小时前
Flutter 可以更直接地调用原生 API 了,Dart Direct Native Interop
android·前端·flutter
杨凯凡1 小时前
Spring Boot 的 @Transactional 为什么会失效?从代理、自调用到异常回滚的排查实战
java·spring boot·spring·事务·后端开发
Data analyse4561 小时前
web-vitals库接入:LCP、INP、CLS上报与阈值
前端·javascript·数据分析
北城笑笑1 小时前
Vue 106 ,上篇:前端桌面端应用实现方案( Electron 框架详细介绍,Vue + Electron )
前端·javascript·vue.js·python·electron
曼岛_1 小时前
[图解密码学]第5章 对称加密基础
java·数据库·密码学