在 LLVM/MLIR 官方的Toy Tutorial(从零构建一个 Toy 语言编译器的经典教程)中,!toy.struct是第 7 章(Chapter 7: Extending Toy: Adding support for a composite type)的核心主角。
如果有人在写关于!toy.struct的技术博客,通常是在复盘或分享“如何为 MLIR 自定义方言(Dialect)添加复合数据类型(Composite Type/结构体)”的深度实践。
为了让你全面了解这个主题,我们可以从概念背景、核心代码实现、 lowering 流程这三个硬核技术栈来拆解这篇博客的内容:
1. 为什么是!toy.struct?(引入背景)
在前 6 章的教程中,Toy 编译器的底层 IR 只有一种数据类型:tensor(张量)。
比如tensor<2x3xf64>表示一个2 × 3 2 \times 32×3的双精度浮点数矩阵。
但是真实的编程语言不可能只有矩阵,必须支持结构体(Struct)。第 7 章的目的就是教你从零在 MLIR 中实现一个自定义类型——!toy.struct。
// 最终在 MLIR 中呈现的样子:一个包含两个元素的未命名结构体 !toy.struct<tensor<*xf64>, tensor<*xf64>>2. 博客的核心硬核技术内容
如果这篇博客写得足够专业,它一定会详细拆解如何在 C++ 和 TableGen(ODS 框架)里把!toy.struct给“挖”出来。步骤通常如下:
① 在 ODS (TableGen) 中定义类型
在ToyTypes.td文件中,不能再直接用內建的TensorType了,得为它专门用 TableGen 声明:
def Toy_StructType : TypeDef<Toy_Dialect, "Struct"> { let mnemonic = "struct"; let summary = "Toy struct type"; // 定义它的参数:它是由一组 MLIR Type 组成的数组(代表结构体的成员) let parameters = (ins ArrayRefParameter<"Type">:$elementTypes); }② C++ 中的 C++ 存储层(Type Storage)
在 MLIR 中,所有 Type 都是不可变的(Immutable)且被全局唯一化(Unique)的。为了在内存中存储结构体的成员信息,博客会介绍如何继承Type::TypeBase并实现StructTypeStorage:
- Unique 机制:确保两个包含同样成员的结构体,在内存里指向同一个地址。
③ 编写解析器与打印机(Parser & Printer)
MLIR 是以文本形式(.mlir)展示的,必须实现 C++ 钩子函数来处理文本转换:
- Printer:负责把内存里的结构体对象打印成类似
!toy.struct<tensor<2xf64>>的字符串。 - Parser:负责在读取
.mlir文件时,把这一串字符串重新解析为内存对象。
3. 围绕!toy.struct扩展的编译器算子(Ops)
单有类型还不够,博客必然会介绍随之诞生的两个关键 Op,用来在计算图里操作这个结构体:
toy.struct_constant:用于初始化结构体常量的算子。toy.struct_access:用于访问结构体成员的算子(比如 C 语言里的a.b中的.操作)。
在 MLIR IR 中长这样:
// 1. 初始化结构体 %0 = "toy.struct_constant"() {value = [dense<1.0> : tensor<1xf64>]} : () -> !toy.struct<tensor<1xf64>> // 2. 访问结构体的第 0 个成员 %1 = "toy.struct_access"(%0) {index = 0 : i64} : (!toy.struct<tensor<1xf64>>) -> tensor<1xf64>4. 博客的终点:如何做 Lowering(阶段性降低)?
这篇博客最精彩的部分往往在于:结构体在降级到低层(如 LLVM IR)时,会发生什么?
在第 6 章中,高层的tensor已经被 Lower 成低层的llvm.struct(包含数据指针、对齐信息的 MemRef 结构)。
到了第 7 章,!toy.struct作为复合类型,它的 Lowering 逻辑会变成“嵌套的结构体”:
- 高层:
!toy.struct<tensor<f64>, tensor<f64>> - 降低到 LLVM Dialect:变成一个
llvm.struct,里面包含两个子llvm.struct(即两个 MemRef 的描述符)。
// 最终编译生成的底层 C 视角伪代码类似于:structToyStruct{structMemRefDescriptorelement_0;structMemRefDescriptorelement_1;};在 Lowering Pass 中,原本的toy.struct_access算子,会被转换成 LLVM Dialect 中的llvm.extractvalue指令,直接根据偏移量(Index)从结构体中抽取出对应成员的内存指针。