本文档是为了快速入门方言对MLIR的属性与类型系统的特定扩展。虽然本教学的主要内容集中在对类型的定义,但是对于定于属性来讲,这些指令几乎是相同的。
类型(Types)
MLIR中的类型(包括 属性,位置,和其他很多东西)都是值类型。这意味着Type
的示例是按值传递的,而不是按指针或者引用传递。Type
class1本身充当内部存储对象的包装器,这个存储对象在一个MLIRContext
实例中是唯一的。
定义Type类(Defining the type class)
根据上面的描述,Type对象在MLIR中是值类型的,且依赖于拥有一个隐式的内部存储对象,该对象保存该类型的实际数据。定义一个新的Type
,没有必要一定定义一个新的存储类(这玩意儿就是上面说的内部存储对象的class)。所以,在定义派生Type
之前,知道我们将要定义的Type
属于哪一种类型是十分重要的:
有些类型在本质上是单例(sigleton),这意味着它们没有参数并且只有一个实例,比如索引类型(index type)。
其他类型是参数化的(parametric),它们包含额外的信息,用于区分同一个Type
的不同实例(这里的实例,指的是类型)。举个例子,integer type
包含了位宽信息,i8与i6代表了integer type
的两个不同实例。
参数化类型还可以包含可变组件,例如,可以使用该组件来构造自引用递归类型(Parametric may also contain a mutable component, which can be used, for example, to construct self-referring recursive types. 这是原文,我没看明白含义)。可变组件不能用于区分Type
类的实例,因此包含可变组件的参数化类型通常包含用于标识它们的其他参数组件。
单例类型(Singleton types)
对于单例类型,我们可以直接跳到定义type
类的步骤。由于单例类型只可能存在一个实例(这里的实例指的是类型的实例),因此不需要提供我们自己的存储类。
/// This class defines a simple parameterless singleton type. All derived types
/// must inherit from the CRTP class 'Type::TypeBase'. It takes as template
/// parameters the concrete type (SimpleType), the base class to use (Type),
/// the internal storage class (the default TypeStorage here), and an optional
/// set of type traits and interfaces(detailed below).
/// 该类定义了一个简单的无参单例类型。
/// 所有的types派生类都必须继承于CRTP类-'Type::TypeBase'。
/// 'Type::TypeBase'将具体类型(SimpleType)、要使用的基类(Type)、
/// 内部存储类(这里默认TypeStorage)和一组可选的类型特征和接口(下面详细介绍)作为模板参数。
class SimpleType : public Type::TypeBase<SimpleType, Type, TypeStorage> {
public:
/// Inherit some necessary constructors from 'TypeBase'.
/// 从'TypeBase'继承一些必要的构造函数。
using Base::Base;
/// The `TypeBase` class provides the following utility methods for
/// constructing instances of this type:
/// 'TypeBase'类提供以下实用程序方法去构建该type的一个实例
/// static SimpleType get(MLIRContext *ctx);
};
参数化类型(Parametric types)
参数化类型是那些具有附加结构或独特约束的类型,允许表达为一个类的不同实例。因此,参数化类型需要定义一个类型存储类来包含参数化类型的参数数据。
定义一个类型存储(Defining a type storage)
类型存储对象包含一个参数化类型实例能被构建且唯一的所有必要参数。存储类必须遵循以下规则:
- 继承基础类型存储类-‘TypeStorage’
- 定义一个类型别名,
KeyTy
,作为该Type派生类实例的唯一标识。 - 提供一个构造方法,存储类将会使用这个方法分配新实例。
static Storage *construct(TypeStorageAllocator &, const KeyTy &key)
- 提供一个方法用于存储类(的
KeyTy
)与KeyTy
的比较bool operator==(const KeyTy &) const
- 提供一个方法,用一组参数生成一个
KeyTy
传递给唯一器?(注意:这不是必须的,除非KeyTy不能用这些参数默认构建)static KeyTy getKey(Args...&& args)
- 提供一个方法,用于散列KeyTy的实例。(注意:这不是必须的,如果存在一个专门的llvm::DenseMapInfo<KeyTy>)
static llvm::hash_code hasKey(const KeyTy &)
来看一个存储类的例子:
/// Here we define a storage class for a ComplexType, that holds a non-zero
/// integer and an integer type.
/// 我们在这里为ComplexType定义一个存储类,它持有一个unsigned和一个Type
/// 注意这里的Type是MLIR中的一种类型,并非我们文章中提到的目标Type
struct ComplexTypeStorage : public TypeStorage {
ComplexTypeStorage(unsigned nonZeroParam, Type integerType)
: nonZeroParam(nonZeroParam), integerType(integerType) {}
/// The hash key for this storage is a pair of the integer and type params.
/// 对于这个存储类来讲,它的KeyTy的类型是一个unsigned与KeyTy的键值对。
using KeyTy = std::pair<unsigned, Type>;
/// Define the comparison function for the key type.
/// 为KeyTy类型创建比较函数
bool operator==(const KeyTy &key) const {
return key == KeyTy(nonZeroParam, integerType);
}
/// Define a hash function for the key type.
/// Note: This isn't necessary because std::pair, unsigned, and Type all have
/// hash functions already available.
/// 为KeyTy定义一个散列函数。
/// 在这个场景里,这个函数是不必要的,因为std::pair,unsigned,Type都拥有散列函数。
static llvm::hash_code hashKey(const KeyTy &key) {
return llvm::hash_combine(key.first, key.second);
}
/// Define a construction function for the key type.
/// Note: This isn't necessary because KeyTy can be directly constructed with
/// the given parameters.
/// 为KeyTy定义一个构造函数
/// 在这个场景里,这个函数不是必要的,因为可以通过unsigned,Type,std::pair直接构造KeyTy
static KeyTy getKey(unsigned nonZeroParam, Type integerType) {
return KeyTy(nonZeroParam, integerType);
}
/// Define a construction method for creating a new instance of this storage.
/// 定义一个构造方法,用于生成该存储类的新实例
static ComplexTypeStorage *construct(TypeStorageAllocator &allocator,
const KeyTy &key) {
return new (allocator.allocate<ComplexTypeStorage>())
ComplexTypeStorage(key.first, key.second);
}
/// The parametric data held by the storage class.
unsigned nonZeroParam;
Type integerType;
};
定义Type类(Type class define)
现在存储类已经被创建,可以开始定义Type派生类了。结构与单例类型相似,不同之处在于使用了更多Type::TypeBase提供的功能。
/// This class defines a parametric type. All derived types must inherit from
/// the CRTP class 'Type::TypeBase'. It takes as template parameters the
/// concrete type (ComplexType), the base class to use (Type), the storage
/// class (ComplexTypeStorage), and an optional set of traits and
/// interfaces(detailed below).
/// 该类定义了一个参数化类型。
/// 所有派生类型都必须继承'Type::TypeBase'。
/// 'Type::TypeBase'将具体类型(SimpleType)、要使用的基类(Type)、
/// 内部存储类(这里默认TypeStorage)和一组可选的类型特征和接口(下面详细介绍)作为模板参数。
class ComplexType : public Type::TypeBase<ComplexType, Type,
ComplexTypeStorage> {
public:
/// Inherit some necessary constructors from 'TypeBase'.
/// 从'TypeBase'继承一些必要的构造函数
using Base::Base;
/// This method is used to get an instance of the 'ComplexType'. This method
/// asserts that all of the construction invariants were satisfied. To
/// gracefully handle failed construction, getChecked should be used instead.
/// 该方法用于获得'ComplexType'的一个实例。
/// 该方法断言所有的构造不变量都满足(不知道是真的会去断言,还是要让调用者保证)。
/// 要优雅的处理失败的构造,应该使用getChecked
static ComplexType get(unsigned param, Type type) {
// Call into a helper 'get' method in 'TypeBase' to get a uniqued instance
// of this type. All parameters to the storage class are passed after the
// context.
// 调用'TypeBase'中的助手'get'方法来获得该类型的唯一实例。
// 通过context会将所有参数传给存储类?
// 这里有两个个问题:type.getContext()得到的应该是一个MLIRContext
// 1、如果我的元素类型不包含Type,那么我怎么获取一个全局的MLIRContext
// 2、为什么我能从传进来的Type拿到MLIRContext
return Base::get(type.getContext(), param, type);
}
/// This method is used to get an instance of the 'ComplexType'. If any of the
/// construction invariants are invalid, errors are emitted with the provided
/// `emitError` function and a null type is returned.
/// Note: This method is completely optional.
/// 这个方法用于获得一个'ComplexType'的实例。
/// 如果存在构造不变量是无效的,错误将会被发射给`emitError`方法,且getChecked返回 Null Type
static ComplexType getChecked(function_ref<InFlightDiagnostic()> emitError,
unsigned param, Type type) {
// Call into a helper 'getChecked' method in 'TypeBase' to get a uniqued
// instance of this type. All parameters to the storage class are passed
// after the context.
return Base::getChecked(emitError, type.getContext(), param, type);
}
/// This method is used to verify the construction invariants passed into the
/// 'get' and 'getChecked' methods. Note: This method is completely optional.
/// 该方法用于验证传给'get'和'getChecked'的构造不变量。
static LogicalResult verify(function_ref<InFlightDiagnostic()> emitError,
unsigned param, Type type) {
// Our type only allows non-zero parameters.
if (param == 0)
return emitError() << "non-zero parameter passed to 'ComplexType'";
// Our type also expects an integer type.
if (!type.isa<IntegerType>())
return emitError() << "non integer-type passed to 'ComplexType'";
return success();
}
/// Return the parameter value.
/// 这两个函数是不必要的,想要要拿到构建存储对象的元素的话,就可以这么实现。
unsigned getParameter() {
// 'getImpl' returns a pointer to our internal storage instance.
return getImpl()->nonZeroParam;
}
/// Return the integer parameter type.
IntegerType getParameterType() {
// 'getImpl' returns a pointer to our internal storage instance.
return getImpl()->integerType;
}
};
可变组件类型(Mutable types)
含有一个可变组件的Type是参数化类型的一个特殊实例,允许在构造后改变某些参数。
定义一个类型存储(Defining a type storage)
除了类型化参数对类型存储类的要求之外,具有可变组件的类型存储类还必须遵守以下规定。
- 可变组件不能是
KeyTy
的一部分 - 提供一个可变方法用于修改已存在的类型存储实例。该方法通过参数来修改可变组件,任何新的动态分配存储都要使用
allocator
,该方法还需要标识修改是否成功- LogicalResult muate(StorageAllocator &allocator, Args …&& args)
定义一个简单的递归类型(recursive types)的类型存储类,通过名字来标识该类型,且可能包括另一个类型(包括它自己):
/// Here we define a storage class for a RecursiveType that is identified by its
/// name and contains another type.
/// 为RecursiveType定义一个类型存储类,用名字标识该类,且包其他类型。
struct RecursiveTypeStorage : public TypeStorage {
/// The type is uniquely identified by its name. Note that the contained type
/// is _not_ a part of the key.
/// 通过名字来唯一标识该类。注意,包含类型不是key的一部分
using KeyTy = StringRef;
/// Construct the storage from the type name. Explicitly initialize the
/// containedType to nullptr, which is used as marker for the mutable
/// component being not yet initialized.
/// 通过类型名来构造类型存储实例。显示的将包含类型初始化为空指针,
/// 标识可变组件还没有被初始化。
RecursiveTypeStorage(StringRef name) : name(name), containedType(nullptr) {}
/// Define the comparison function.
/// 对于比较函数而言,MLIR并没有禁止body参与比较!
bool operator==(const KeyTy &key) const { return key == name; }
/// Define a construction method for creating a new instance of the storage.
static RecursiveTypeStorage *construct(StorageAllocator &allocator,
const KeyTy &key) {
// Note that the key string is copied into the allocator to ensure it
// remains live as long as the storage itself.
return new (allocator.allocate<RecursiveTypeStorage>())
RecursiveTypeStorage(allocator.copyInto(key));
}
/// Define a mutation method for changing the type after it is created. In
/// many cases, we only want to set the mutable component once and reject
/// any further modification, which can be achieved by returning failure from
/// this function.
/// 创建一个可变函数用于改变该类型在类型存储实例创建之后。
/// 在许多情况下,我们只需要设置可变组件一次,拒绝再次修改。
/// 这可以通过从该函数返回失败来实现。
LogicalResult mutate(StorageAllocator &, Type body) {
// If the contained type has been initialized already, and the call tries
// to change it, reject the change.
if (containedType && containedType != body)
return failure();
// Change the body successfully.
containedType = body;
return success();
}
StringRef name;
Type containedType;
};
定义Type类
现在我们已经有了类型存储类,可以定义Type类本身了。Type::TypeBase
提供了一个mutate
方法,该方法将其参数转发给类型存储类的mutate
方法,并确保安全地发生突变(不明白啥叫突发)
class RecursiveType : public Type::TypeBase<RecursiveType, Type,
RecursiveTypeStorage> {
public:
/// Inherit parent constructors.
using Base::Base;
/// Creates an instance of the Recursive type. This only takes the type name
/// and returns the type with uninitialized body.
static RecursiveType get(MLIRContext *ctx, StringRef name) {
// Call into the base to get a uniqued instance of this type. The parameter
// (name) is passed after the context.
return Base::get(ctx, name);
}
/// Now we can change the mutable component of the type. This is an instance
/// method callable on an already existing RecursiveType.
/// 现在我们可以改变该类型的可变组件。
/// 这是一个在以有的RecursiveType实例上调用的实例方法。
void setBody(Type body) {
// Call into the base to mutate the type.
// 调用base提供的mutate方法使该类型变化。
LogicalResult result = Base::mutate(body);
// Most types expect the mutation to always succeed, but types can implement
// custom logic for handling mutation failures.
// 大多数类型都是期望能够变化成功的,但类型仍然要实现可以处理变化失败时的自定义逻辑。
assert(succeeded(result) &&
"attempting to change the body of an already-initialized type");
// Avoid unused-variable warning when building without assertions.
(void) result;
}
/// Returns the contained type, which may be null if it has not been
/// initialized yet.
Type getBody() {
return getImpl()->containedType;
}
/// Returns the name.
StringRef getName() {
return getImpl()->name;
}
};
在方言中注册类型(Registering types with a Dialect)
一旦定义了方言中的类型,就必须把它们注册到一个方言Dialect
中。通过和注册Op类似的机制来注册类型,借助addTypes
方法。与Op明显不同的是,当注册一个Type
时,其对应的TypeStorage
类定义必须是可见的。
struct MyDialect : public Dialect {
MyDialect(MLIRContext *context) : Dialect(/*name=*/"mydialect", context) {
/// Add these defined types to the dialect.
/// 这个addTypes的模板参数与玩具语言中的实例不同,得去看一下源码。
addTypes<SimpleType, ComplexType, RecursiveType>();
}
};
解析与打印(Parsing and Printing)
作为注册的最后一步,必须重写方言的printType
与parseType
这两个钩子函数。它们支持Type
能在以.mlir
结尾的文本中双向翻译。
class MyDialect : public Dialect {
public:
/// Parse an instance of a type registered to the dialect.
Type parseType(DialectAsmParser &parser) const override;
/// Print an instance of a type registered to the dialect.
void printType(Type type, DialectAsmPrinter &printer) const override;
};
这些方法需要一个高等级的parser或者printer,这样可以轻松的拿到某些必要的功能。根据MLIR language reference,方言类型的通用格式应形如:!dialect-namespace<type-data>
,在某些情况下能够保持一个漂亮的格式。parser与printer的职责就是提供表达式中的type-data
。
特征(Traits)
与Op相似,Type
类也可能附加Traits
,来提供额外的mixin方法和其他类型。Traits
类可以通过Type::TypeBase
类的模板参数指定。更多关于Trait
的定义与使用在Trait
文档中有描述。
接口(Interfaces)
与Op相似,Type
类可以附加Interfaces
为该类型提供一个抽象接口。更多关于Interfaces
的定义与使用在Interfaces
文档中有描述。
属性(Attributes)
如引言所示,定义方言属性的过程与定义方言类型的过程几乎相同。关键的区别是在方言类型定义过程中出现的*Type
现在都要被*Attr
代替。
- Type::TypeBase -> Attribute::AttrBase
- TypeStorageAllocator -> AttributeStorageAllocator
- addTypes -> addAttributes
除此之外,所有用于唯一化实例和存储构造的接口都相同。
这里使用class,而不是翻译成中文(类),是为了表达在MLIR中的一种Type看起来就是一个class,只不过这个class不能完全的描述这个Type,还需要借助内部存储对象。 ↩︎