
本篇文章主要基于mlir的公开talk,深入理解一下mlir中的operation,attributes以及properties机制的底层实现。本文为此系列的第二篇,着重解读mlir的attirbutes特性。
Attribute初探
首先,我们需要先简要理解一下Attribute在MLIR编译器中的作用。根据MLIR的官方文档资料,Attribute是在一个operation中用来表达常量数据的机制。MLIR中的每一operation都关联一个属性字典,其中包含属性名称和属性值的映射。
- 属性按照所属方言划分,可以分为内嵌属性和方言属性。
- 内嵌属性:mlir的内嵌方言提供的属性,比如我们常用的StringAttr,DenseArryAttr等。
- 方言属性:允许方言中定义自己的属性
- 属性按照字典键是否又方言前缀,可以分为inherent attributes和discardable attributes。
- inherent属性:该属性由operation的语义信息来决定
- discardable属性:属性自带语义,但要和operation相一致
如下图所示就是两种attribute的典型示例。cmpi操作的谓词属性和操作严格捆绑。
Attribute 底层原理

上图是编写mlir的时候,经常用到的创建新的attribute。该逻辑是,先构造构造一个vector,然后调用get() API获取一个属性的指针。这里的get() api的特点是,如果已有DenseI64ArrayAttr的内容是offsetsVec,则返回已有的属性,否则创建返回。
Attribute的recap知识
首先来总结一下attribute的一些特点:
其最核心的一个特点是属性是不可变的,因此其一系列底层机制都围绕不可变性展开。获取一个内容是offsetsVec的DenseI64ArrayAttr,有如下几步骤:
- 在operation所在的MLIRContext中获取DenseI64Array的ParametricStorageUniquer(每个属性类型都由一个存储器管理)
- 在该storageuniqure中,通过hash的方式查找(key是<Type, int64, llvm::ArrayRef
>>)。
Attribute的get()方法底层逻辑链条
首先我们写梳理整个逻辑链条相关的数据结构:
get方法返回什么
get()方法返回的是DenseArrayAttrStorage指针,该指针包含实际存储内容和一个hash值。
- 实际存储内容包含:存储数据类型,存储数据长度,数组内容。
- KeyTy表示hash值,是type,数据长度和实际数组的一个元组。

通过属性类型:DenseI64ArrayAttr找寻到该类型的底层storage

如上图所示,一个context上下文中,每个属性都有对应的storage。

用一个densemap存储,每个attribute都有一个type id,通过type id检索到对应的ParametricStorageUniqer指针。
从ParametricStorageUniqer中通过hash值创建/返回属性指针

如下是一个完整的get流程:

值得注意的是,底层的存储BumpPtrAllocator类似于内存池,只能开辟,然后空间释放是整个Allocator管理的所有存储都释放。
Operation Accessors

上图中是一个operation常用的api,其中,Operand,Result和region都是可变的,而attr是不可变的。因此看似api都是get,set,但实际底层机制attr会复杂的多。
具体复杂性体现在如下slide中:
由于不可变,因此set attr的时候,针对DictionaryAttr中添加一组name和attrvalue,底层也是十分复杂的(DictionaryAttr也是属性)。
- 首先将已有的dict拷贝入数组
- 在数组中变化,添加新的value(inplace)
- 用get方法生成新的Dictionary
一个简单的setAttr,底层流程分为如上三步,attr的immutable特性,导致其效率较低。
上述slide是一个很好地总结。
如下是一个检测理解的例子:
显而易见,这里存在大量的vector拷贝, 以及可能的存储空间浪费。



