常量折叠

通过前面几节的学习,我们已经完成了简单语言的实现,并且增加了对LLVM IR生成的支持,这章我们将引入两项新的技术:编译优化支持和JIT编译支持。这些新增内容将演示如何为Kaleidoscope语言生成优质,高效的代码。

我们对第3章的演示优雅且易于扩展。不幸的是,它不会产生很棒的代码。但是,在编译简单代码时,IRBuilder确实给了我们明显的优化:

ready> def test(x) 1+2+x;
Read function definition:
define double @test(double %x) {
entry:
        %addtmp = fadd double 3.000000e+00, %x
        ret double %addtmp
}

此代码不是通过解析输入构建的AST的文字转录。那将是:

ready> def test(x) 1+2+x;
Read function definition:
define double @test(double %x) {
entry:
        %addtmp = fadd double 2.000000e+00, 1.000000e+00
        %addtmp1 = fadd double %addtmp, %x
        ret double %addtmp1
}

如上所述,常量折叠是一种非常常见且非常重要的优化:以至于许多语言实现者在其AST表示中实现常量折叠支持。

使用LLVM,您不需要AST中的此支持。由于构建LLVM IR的所有调用都通过LLVM IR构建器,因此构建器本身会在调用时检查是否存在持续折叠机会。如果是这样,它只是执行常量折叠并返回常量而不是创建指令。

嗯,这很容易:)。在实践中,我们建议IRBuilder在生成这样的代码时始终使用 。它的使用没有“语法开销”(你不必在任何地方使用常量检查来编译你的编译器)并且它可以大大减少在某些情况下生成的LLVM IR的数量(特别是对于具有宏预处理器的语言或使用很多常量)。

另一方面,IRBuilder它受到以下事实的限制:它在构建时与代码内联进行所有分析。如果你采取一个稍微复杂的例子:

ready> def test(x) (1+2+x)*(x+(1+2));
ready> Read function definition:
define double @test(double %x) {
entry:
        %addtmp = fadd double 3.000000e+00, %x
        %addtmp1 = fadd double %x, 3.000000e+00
        %multmp = fmul double %addtmp, %addtmp1
        ret double %multmp
}

在这种情况下,乘法的LHS和RHS是相同的值。我们期望看到生成这样的代码

tmp = x+3; result = tmp*tmp;而不是生成“x+3”两次。

不幸的是,没有多少本地分析能够检测并纠正这一点。这需要两个转换:表达式的重新关联(使加法的词法相同)和通用的Subexpression Elimination(CSE)删除冗余的加法指令。幸运的是,LLVM以“Passes”的形式提供了您可以使用的各种优化。