!toy.struct

!toy.struct

在 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,用来在计算图里操作这个结构体:

  1. toy.struct_constant:用于初始化结构体常量的算子。
  2. 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)从结构体中抽取出对应成员的内存指针。