使用 C++ 风格的模式匹配和重写来优化转置运算
使用 DRR 优化 reshape 运算
创建一种贴近输入语言的语义表示的方言,可以在 MLIR 中分析、变换和优化,这些过程中需要用到高级语言的信息,而且通常是在语言的 AST 上执行的这些过程。
例如,为了执行template 实例化,在clang 程序中有一个相当厚实的机制。
我们将编译器变换分为两大类:局部的和全局的。在本章中,我们聚焦怎么利用Toy Dialect 和它的高层次语义来执行局部模式匹配变换,这在 LLVM 中是比较难实现的。
为此,我们使用了 MLIR的 Generic DAG Rewriter。
这里有两个方法可以用于实现模式匹配变换:1,命令式的,C++模式匹配和重写,2,生命式的,基于规则的模式匹配和重写,这是使用 表格驱动的 Declarative Rewrite Rules(DRR)。
注意,使用DRR的话,要求 operations 是使用 ODS 定义的,如第2章中所述。
使用 C++ 风格的模式匹配和重写来优化转置运算
让我们先一起研究一个简单的模式,并且尝试去消除一个由两个可以抵消的转置组成的序列:transpose(transpose(X)) -> X。这里是对应的 Toy 示例:
def transpose_transpose(x) {
return transpose(transpose(x));
}
它对应于如下的 Toy IR:
toy.func @transpose_transpose(%arg0: tensor<*xf64>) -> tensor<*xf64> {
%0 = toy.transpose(%arg0 : tensor<*xf64>) to tensor<*xf64>
%1 = toy.transpose(%0 : tensor<*xf64>) to tensor<*xf64>
toy.return %1 : tensor<*xf64>
}
这个变换的好示例,在Toy IR 中做匹配非常容易,但是,在 LLVM IR 中解决这个问题非常困难。例如,当前的 Clang 无法优化掉临时数组,并且单纯的转置的计算,表示为如下这样的循环:
#define N 100
#define M 100
void sink(void *);
void double_transpose(int A[N][M]) {
int B[M][N];
for(int i = 0; i < N; ++i) {
for(int j = 0; j < M; ++j) {
B[j][i] = A[i][j];
}
}
for(int i = 0; i < N; ++i) {
for(int j = 0; j < M; ++j) {
A[i][j] = B[j][i];
}
}
sink(A);
}
对于一个简单实现重写的 C++ 方法,涉及到在 IR 中匹配一个类树的模式,并且用一组不同的 operations 集合来替代它,我们可以通过实现一个 RewritePattern来插入到 MLIR Canonicalizer 中。
未完待续。。。