LLVM尾调用优化

时间:2013-09-04 00:03:39

标签: llvm tail-recursion llvm-ir

以下是我对事物的理解:

函数“f”在调用自身时是尾递归是它的最后一个动作。 通过形成循环而不是再次调用函数,可以显着优化尾递归;函数的参数就地更新,然后再次运行正文。这称为递归尾调用优化。

LLVM在使用fastcc,GHC或HiPE调用约定时实现递归尾调用优化。 http://llvm.org/docs/CodeGenerator.html#tail-call-optimization

我有一些问题: 让我们考虑一下这个愚蠢的例子:

int h(int x){
  if (x <= 0)
    return x;
  else
    h(x-1);
}

1)在他们的例子中,关键字“tail”在调用之前。在其他地方,我读到这个关键字是可选的。假设上面的函数被适当地转换为LLVM,最后几行需要

%x' = load *i32 %x
%m = tail call fastcc i32 @h(i32 %x')
ret %m

2)示例中inreg选项的含义是什么?

3)我不希望在整个地方执行尾调用优化,仅用于递归函数。有没有办法让LLVM只对递归函数进行优化(如果可用)?

2 个答案:

答案 0 :(得分:2)

显然答案是肯定的。你必须改变h的定义才能看到这个(因为优化器太好了!它确定h是标识或返回0)。

考虑

int factorial (int x, int y){
  if (x==0)
    return y;
  else
    return factorial(x-1,y*x);
}

使用clang -S -emit-llvm编译,因此不执行任何优化。可以看出没有直接指定调用约定,这意味着默认调用约定足以支持尾递归优化(无论它是否支持尾调用都是一个不同的故事 - 知道它会很有趣,但我想这真是一个不同的问题。)

clang -S -emit-llvm发出的文件是main.s(假设factorial定义在main.c中)。如果你运行

opt -O3 main.s -S -o mainOpt.s

然后你可以看到尾部递归被消除了。有一个名为tailcallelim的优化可以打开为-O3。这很难分辨,因为帮助文件opt --help只表示-O3与gcc -O3类似。

关键是我们可以看到调用约定不需要为此指定。也许不需要fastcc,或者它可能是默认的?所以(1)部分回答;但是,我仍然不知道(2)或(3)。

答案 1 :(得分:2)

这里有两件不同的事情:

  1. 您可以将自递归尾调用优化为循环。 LLVM提供了执行此操作的优化过程。它不需要特定的调用约定。
  2. 您可以使用不同的调用约定来保证尾部位置的所有调用的尾调用优化(即包括对其他函数的调用)。使用LLVM,您需要在函数上指定调用约定,在调用指令上并将调用标记为尾调用。
  3. 听起来你想要前者。