算子规范化
规范化是编译器IR设计的重要组成部分:它使实现可靠的编译器转换和确定代码中优劣的原因变得更加容易,并且使有关IR特定级别的目标的讨论变得更加有趣。丹·高曼(Dan Gohman)写了一篇文章 探讨这些问题。如果不熟悉这些概念,则值得阅读。
大多数编译器都有规范化的遍历,有时它们有很多不同的遍历(例如LLVM中的instcombine,dag Combine等)。因为MLIR是一个多级IR,所以可以提供一个规范的基础架构,并在它代表的许多不同IR中重用它。本文介绍了通用方法,执行全局规范化,并提供了一些内容,以捕获特定于IR的规则以供参考。
总体设计
MLIR具有一次规范化遍历,它以贪婪的方式迭代地应用规范化转换,直到IR收敛为止。这些转换由算子本身定义,允许每个语言一起定义自己的一组算子和规范化。
关于wrt规范化模式的一些重要事项:
- 模式的重复应用应收敛。不稳定或周期性的重写将导致规范化器中的无限循环。
- 通常对重复算子时规范化,最好使用较少值的算子,因为某些模式仅在值具有单个用户时才匹配。例如,通常最好将“ x + x”规范化为“ x * 2”,因为这样可以将x的使用次数减少一半。
- 在可能的情况下,最好完全消除算子,例如折叠已知身份(例如“ x + 0 = x”),这总是好事。
在全局范围内适用的规则
这些转换适用于所有级别的IR:
- 消除无副作用,无用的算子。
- 不断折叠-例如,“(addi 1,2)”到“ 3”。固定折叠钩由算子指定。
- 将常量算子移动到可交换运算符的右侧-例如,将“(addi 4,x)”移动到“(addi x,4)”。
- constant-like算子是唯一的,并被提升到第一父屏障区域的入口块中。这是一个与上方隔离的区域,例如功能的输入框,或者是通过shouldMaterializeInto方法标记为障碍的区域DialectFoldInterface。
定义Canonicalizations
有两种定义规范的机制。 getCanonicalizationPatterns和fold。
用getCanonicalizationPatterns规范化
这种机制允许以RewritePatterns的形式提供规范化,这些规范化是 在C ++中强制性定义的,或者是声明性地称为 “声明性重写规则” 。模式重写基础结构允许表达许多不同类型的规范化。这些转换可能很简单,例如用移位替换乘法,甚至用无条件分支替换条件分支。
在 ODS中 ,算子可以设置该hasCanonicalizer位,生成getCanonicalizationPatterns方法的声明。
def MyOp : ... {
let hasCanonicalizer = 1;
}
然后可以在源文件中提供规范化模式:
void MyOp::getCanonicalizationPatterns(OwningRewritePatternList &patterns,
MLIRContext *context) {
patterns.insert<...>(...);
}
有关 定义算子重写的信息,请参见 快速入门指南。
用规范化fold
该fold机制是有意限制的,但是功能强大的机制允许在整个编译器的许多位置应用规范化。例如,canonicalizer pass外部,fold在所述内使用 语言转换基础结构 作为合法化机制,并且可以经由 OpBuilder::createOrFold用在任何地方直接调用OpBuilder。
fold具有不能创建任何新算子的限制,并且只能替换根算子。它允许就地更新算子,或返回一组预先存在的值(或属性)来替换算子。这样可以确保该fold方法是真正的“本地”转换,并且可以在不需要模式重写器的情况下调用该方法。
在 ODS中 ,算子可以设置该hasFolder位以生成fold方法的声明。此方法采用不同的形式,具体取决于算子的结构。
def MyOp : ... {
let hasFolder = 1;
}
如果算子只有一个结果,则将生成以下内容:
/// Implementations of this hook can only perform the following changes to the
/// operation:
///
/// 1. They can leave the operation alone and without changing the IR, and
/// return nullptr.
/// 2. They can mutate the operation in place, without changing anything else
/// in the IR. In this case, return the operation itself.
/// 3. They can return an existing value or attribute that can be used instead
/// of the operation. The caller will remove the operation and use that
/// result instead.
///
OpFoldResult MyOp::fold(ArrayRef<Attribute> operands) {
...
}
否则,将生成以下内容:
/// Implementations of this hook can only perform the following changes to the
/// operation:
///
/// 1. They can leave the operation alone and without changing the IR, and
/// return failure.
/// 2. They can mutate the operation in place, without changing anything else
/// in the IR. In this case, return success.
/// 3. They can return a list of existing values or attribute that can be used
/// instead of the operation. In this case, fill in the results list and
/// return success. The results list must correspond 1-1 with the results of
/// the operation, partial folding is not supported. The caller will remove
/// the operation and use those results instead.
///
LogicalResult MyOp::fold(ArrayRef<Attribute> operands,
SmallVectorImpl<OpFoldResult> &results) {
...
}
在上面,为每种方法ArrayRef<Attribute>提供了一个与每个算子的常量属性值相对应的。这些算子是实现ConstantLike特征的算子。如果任何一个算子不是常数,则将Attribute提供一个空值。例如,如果提供MYOP三个算子[ a,b,c],但只b是常量,那么operands将是以下形式的[属性(),b值,属性()]。
同样在上面,使用OpFoldResult。此类表示折叠算子结果的可能结果:SSAValue或 Attribute(对于恒定结果)。如果Value提供了SSA ,则它必须 对应于现有值。该fold方法不允许生成new Value。Attribute返回值的形式没有具体限制 ,但重要的是要确保Attribute 具体表示形式的Type一致性。
当fold算子上的钩子算子不成功时,语言可以通过实现DialectFoldInterface和覆盖折叠钩子来提供后备功能。
从属性生成常数
当一个fold方法返回aAttribute作为结果时,表示该结果是“常量”。Attribute是该值的常数表示。该fold方法的用户(例如规范化过程)将采用这些Attributes,并在IR中实现常量算子来表示。为了实现这种实现,算子的语言必须实现materializeConstant钩子。该钩子接受一个Attribute 通常由返回的值,fold并产生实现该值的“类似常数”的算子。
在 ODS中 ,语言可以将hasConstantMaterializer位设置为生成materializeConstant方法的声明。
def MyDialect_Dialect : ... {
let hasConstantMaterializer = 1;
}
然后可以在源文件中实现常量:
/// Hook to materialize a single constant operation from a given attribute value
/// with the desired resultant type. This method should use the provided builder
/// to create the operation without changing the insertion position. The
/// generated operation is expected to be constant-like. On success, this hook
/// should return the value generated to represent the constant value.
/// Otherwise, it should return nullptr on failure.
Operation *MyDialect::materializeConstant(OpBuilder &builder, Attribute value,
Type type, Location loc) {
...
}