算子规范化

规范化是编译器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) {

  ...

}

 

人工智能芯片与自动驾驶