MLIR专题1:创建方言流程(使用ODS)
整个TableGen模块基于ODS(Operation Definition Specification,操作定义规范)来生成代码,
- TableGen:TableGen 是一套“声明式描述语言 + 由 LLVM 提供的代码生成工具链”。
- ODS:MLIR 利用 TableGen 定义 Operation 的一套规则/DSL。
它们解决的问题是:不用手写大量重复的 C++ IR 类、解析器、打印器、验证器代码,而是用.td文件描述,然后自动生成。
我们来看一个创建方言的例子,Ops.td文件的位置:mlir/examples/toy/Ch6/include/toy/Ops.td,通过一下4个步骤构建方言:
定义一个xxx方言:
def Toy_Dialect : Dialect { let name = "toy"; let cppNamespace = "::mlir::toy"; }这段代码用 TableGen 语法定义了一个名为 Toy 的 MLIR 方言(Dialect),逐行解释:
def Toy_Dialect : Dialect { ... }
- def :TableGen 关键字,定义一个 具体的实例 (与 class 定义模板不同)
- Toy_Dialect :实例名称,后续代码通过这个名字引用该方言
- : Dialect :继承自 DialectBase.td 中的 class Dialect 模板
let name = "toy";
- 设置方言的 命名空间名称 为 "toy"
- 这意味着该方言下的操作在 MLIR 文本表示中会以 toy. 为前缀,例如: toy.constant 、 toy.add
let cppNamespace = "::mlir::toy";
- 设置生成的 C++ 代码中,该方言的操作和类型所在的 C++ 命名空间 为 ::mlir::toy
- 生成的 C++ 类(如 ConstantOp )会被放在 namespace mlir { namespace toy { ... } } 中
创建xxx方言的运算基础类
class Toy_Op<string mnemonic, list<Trait> traits = []> : Op<Toy_Dialect, mnemonic, traits>;class Toy_Op<...>
- class :TableGen 关键字,定义一个 模板类 (不是具体实例, def 才是实例)
- Toy_Op :模板名称,后续用 def 定义具体操作时继承它 模板参数 <string mnemonic, list<Trait> traits = []>
| 参数名 | 参数类型 | 默认值 | 参数含义 |
|---|---|---|---|
| mnemonic | string | 无(必填) | 操作的助记符,即操作名(不含方言前缀) |
| traits | list<Trait> | [] | 操作的特征列表,如 Pure、SameOperandsAndResultType 等 |
: Op<Toy_Dialect, mnemonic, traits>
- 继承自 OpBase.td 中定义的 class Op<Dialect, string, list<Trait>>
- Toy_Dialect :将方言固定为 Toy 方言,这样所有继承 Toy_Op 的操作自动属于 Toy 方言
- mnemonic 和 traits :透传给 Op 基类
作用:简化操作定义
没有 Toy_Op 时 ,每个操作都要写完整的 Op<Toy_Dialect, ...> :
def ConstantOp : Op<Toy_Dialect, "constant", [Pure]> { ... } def AddOp : Op<Toy_Dialect, "add", [Pure]> { ... } def MulOp : Op<Toy_Dialect, "mul", [Pure]> { ... }有了 Toy_Op 后 ,方言参数被固定,只需写助记符和特征:
def ConstantOp : Toy_Op<"constant", [Pure]> { ... } def AddOp : Toy_Op<"add", [Pure]> { ... } def MulOp : Toy_Op<"mul", [Pure]> { ... }创建xxx方言的各种运算
常量运算
def ConstantOp : Toy_Op<"constant", [Pure]> { // Provide a summary and description for this operation. This can be used to // auto-generate documentation of the operations within our dialect. let summary = "constant"; let description = [{ Constant operation turns a literal into an SSA value. The data is attached to the operation as an attribute. For example: ```mlir %0 = toy.constant dense<[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]> : tensor<2x3xf64> ``` }]; // The constant operation takes an attribute as the only input. let arguments = (ins F64ElementsAttr:$value); // The constant operation returns a single value of TensorType. let results = (outs F64Tensor); // Indicate that the operation has a custom parser and printer method. let hasCustomAssemblyFormat = 1; // Add custom build methods for the constant operation. These method populates // the `state` that MLIR uses to create operations, i.e. these are used when // using `builder.create<ConstantOp>(...)`. let builders = [ // Build a constant with a given constant tensor value. OpBuilder<(ins "DenseElementsAttr":$value), [{ build($_builder, $_state, value.getType(), value); }]>, // Build a constant with a given constant floating-point value. OpBuilder<(ins "double":$value)> ]; // Indicate that additional verification for this operation is necessary. let hasVerifier = 1; }1. 操作声明
def ConstantOp : Toy_Op<"constant", [Pure]>| 部分 | 含义 |
|---|---|
def ConstantOp | 定义一个具体操作(Operation),名称为ConstantOp |
Toy_Op<"constant", ...> | 继承自Toy_Op,并指定该操作的助记符(mnemonic)为"constant" |
[Pure] | 特征(Trait):表示该操作是纯操作(无副作用,可以被死代码消除等优化) |
在 MLIR 文本中表示为: toy.constant。
2.文档信息
let summary = "constant"; let description = [{ Constant operation turns a literal into an SSA value. The data is attached to the operation as an attribute. For example: ```mlir %0 = toy.constant dense<[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]> : tensor<2x3xf64> ``` }];- summary :一行摘要,用于自动生成文档
- description :详细描述,包含 MLIR 示例代码
3. 输入参数(arguments)
let arguments = (ins F64ElementsAttr:$value);| 部分 | 含义 |
|---|---|
(ins ...) | ins标记这是输入参数(operand/参数)列表 |
F64ElementsAttr | 参数类型:64 位浮点元素属性(Attribute,不是Value) |
:$value | 参数名称为value |
关键点 :常量操作的"输入"不是运行时的值(Value),而是编译期常量属性(Attribute)。生成的 C++ 代码会有 DenseElementsAttr value() 访问器。
4. 输出结果(results)
let results = (outs F64Tensor);| 部分 | 含义 |
|---|---|
(outs ...) | outs标记这是输出结果(result)列表 |
F64Tensor | 结果类型:64 位浮点张量(Tensor 类型) |
5. 自定义汇编格式
let hasCustomAssemblyFormat = 1;表示该操作有 自定义的解析和打印方法 ,不使用自动生成的格式。需要在 C++ 中手动实现 parse 和 print 方法。
6. 自定义构建器(builders)
let builders = [ // 构建器1:接受 DenseElementsAttr OpBuilder<(ins "DenseElementsAttr":$value), [{ build($_builder, $_state, value.getType(), value); }]>, // 构建器2:接受 double OpBuilder<(ins "double":$value)> ];| 构建器参数 | 作用 |
|---|---|
第 1 个DenseElementsAttr value | 从张量属性(DenseElementsAttr)创建常量,内部调用另一个build方法 |
第 2 个double value | 从单个浮点数创建常量 |
- $_builder :代表 OpBuilder &
- $_state :代表 OperationState &
- 这些构建器让用户可以通过 builder.create<ConstantOp>(...) 以不同方式创建操作。
针对两种类型的关系图解如下:
用户代码 Builder arguments (IR 存储) ───────── ──────── ──────────────────── builder.create<ConstantOp>(loc, 3.14) │ ▼ Builder2: double → DenseElementsAttr ← 自动转换 │ ▼ 默认 Builder: (Type, DenseElementsAttr) │ ▼ F64ElementsAttr:$value ← 最终存入 Operation builder.create<ConstantOp>(loc, attr) │ ▼ Builder1: DenseElementsAttr → (Typ