toy.struct_constant和toy.struct_access

📅 2026/7/16 19:39:01
toy.struct_constant和toy.struct_access
在有了类型存储层StructTypeStorage后编译器中还必须有具体的操作Operations简称 Ops来创建和访问这个结构体。在 MLIR Toy Tutorial 中针对!toy.struct自定义类型精心设计了两个互补的核心算子toy.struct_constant和toy.struct_access。它们在计算图中分别扮演着“生产者”与“消费者”的角色。1.toy.struct_constant—— 结构体的“生产者”在编译器中当用户在代码里写下类似于var a [[1, 2], [3, 4]]假设这是一个结构体字面量时编译器需要在一个算子中把这些常量值打包封装起来。toy.struct_constant就是干这个活的。 IR 展现形式它在 MLIR 文本文件中长这样%0 toy.struct_constant() { value [dense[1.0, 2.0] : tensor2xf64, dense3.0 : tensor1xf64] } : () - !toy.structtensor2xf64, tensor1xf64输入它不需要运行时输入参数括号内为空()。属性Attributes它携带了一个编译器静态已知的属性value里面是一个数组包含两个具体的稠密张量常量dense...。输出类型返回一个完整的、带有具体成员类型签名的!toy.struct。️ TableGen (ODS) 定义在Ops.td中它的定义重点在于约束它的value属性和返回值类型def StructConstantOp : Toy_Opstruct_constant, [Pure] { let summary 结构体常量生成算子; // 关键属性存储具体数据的 ArrayAttr let arguments (ins ArrayAttr:$value); // 关键输出输出必须是一个 Toy 结构体类型 let results (outs Toy_StructType:$output); }2.toy.struct_access—— 结构体的“消费者”有了结构体程序总得去读里面的数据。toy.struct_access类似于 C 语言中的点运算符如my_struct.member专门用来根据索引Index提取结构体中的某个特定数组成员。 IR 展现形式// 语义从刚才生成的结构体 %0 中提取索引为 0 的那个成员 %1 toy.struct_access(%0) { index 0 : i64 } : (!toy.structtensor2xf64, tensor1xf64) - tensor2xf64输入接收一个已经生成的!toy.struct变量。属性Attributes携带一个静态的整数属性index表明我们要拿第几个成员从 0 开始计数。输出类型它的输出类型这里是tensor2xf64必须严格等于该结构体中对应索引位置的那个类型。如果类型对不上MLIR 的验证器Verifier就会报错。️ TableGen (ODS) 定义def StructAccessOp : Toy_Opstruct_access, [Pure] { let summary 结构体成员访问算子; // 输入一个结构体对象 一个静态的索引值 let arguments (ins Toy_StructType:$input, I64Attr:$index); // 输出解包后的任意 MLIR 类型通常是底层 tensor let results (outs AnyType:$output); // 必须声明自定义的验证逻辑 let hasVerifier 1; }3. 核心硬核点struct_access的 C 验证器Verifier由于 MLIR 强调强类型安全你不能随意去 access 一个不存在的索引或者宣称拿出来的类型与结构体内部存的类型不一致。因此博客在介绍struct_access时一定会重点贴出它的 Cverify()函数实现LogicalResultStructAccessOp::verify(){// 1. 获取输入结构体的类型指针autostructTypellvm::castStructType(getInput().getType());// 2. 获取你想要访问的 index 值uint64_tindexgetIndex();// 3. 安全检查索引不能越界if(indexstructType.getElementTypes().size())returnemitOpError()访问越界结构体只有 structType.getElementTypes().size() 个成员;// 4. 类型强一致性检查算子声明的输出类型 必须等于 结构体该位置的真实类型Type expectedTypestructType.getElementTypes()[index];if(getOutput().getType()!expectedType)returnemitOpError()类型不匹配期望获得 expectedType但算子输出声明为了 getOutput().getType();returnsuccess();}4. 它们在 Lowering阶段性降低时去哪了当编译器把代码向下推进到LLVM Dialect底层中间表示准备对接libc或生成汇编时这两个高层算子会发生巨大的蜕变toy.struct_constant在高级抽象中它是一体化的。降低到低层时它会被完全解构Flatten。编译器会分别为内部的每一个 Tensor 常量生成底层的llvm.mlir.global全局静态内存并生成一个llvm.struct把这些内存描述符指针打包装进去。toy.struct_access会被直接映射并降低为 LLVM Dialect 的llvm.extractvalue指令。该指令在硬件物理层面非常高效它直接通过相对结构体基地址的偏移量Offset一条指令把目标成员的内存指针给抠出来。 总结struct_constant和struct_access是高级语言复合类型在编译器中间表示IR里的具体体现。一个负责把离散的数据包起来一个负责按图索骥把数据掏出来。有了它们MLIR 才能在高级图层面上完成复杂的结构体数据流分析与优化并在最后一刻安全地交付给底层的llvm.struct和硬件寄存器。