1

使用 clang -O2 (或使用在线演示)编译下一段代码后:

#include <stdio.h>
#include <stdlib.h>

int flop(int x);
int flip(int x) {
  if (x == 0) return 1;
  return (x+1)*flop(x-1);
}
int flop(int x) {
  if (x == 0) return 1;
  return (x+0)*flip(x-1);
}

int main(int argc, char **argv) {
  printf("%d\n", flip(atoi(argv[1])));
}

我正在获得下一个 llvm 程序集片段flip

bb1.i:                                            ; preds = %bb1
  %4 = add nsw i32 %x, -2                         ; <i32> [#uses=1]
  %5 = tail call i32 @flip(i32 %4) nounwind       ; <i32> [#uses=1]
  %6 = mul nsw i32 %5, %2                         ; <i32> [#uses=1]
  br label %flop.exit

我认为这tail call意味着删除当前堆栈(即返回将是上帧,所以下一条指令应该是ret %5),但根据这段代码它会这样做mul。在本机汇编中,call没有尾部优化很简单(即使有适当的 llc 标志)

有人能解释一下为什么clang会生成这样的代码吗?

同样,我不明白为什么 llvmtail call可以简单地检查下一个ret将使用 prev 的结果,call然后进行适当的优化或生成与尾调用指令等效的本机?

4

1 回答 1

3

查看LLVM 汇编语言参考手册中的“调用”指令。它说:

可选的“tail”标记表示被调用函数不访问调用者中的任何 allocas 或 varargs。请注意,调用可能会被标记为“尾部”,即使它们没有在 ret 指令之前发生。

Clang 中的 LLVM 优化传递之一可能会分析被调用者是否访问调用者中的任何分配或可变参数。如果不是,则 pass 将调用标记为尾调用,并让 LLVM 的另一部分找出如何处理“尾”标记。也许这个函数现在不能是一个真正的尾调用,但经过进一步的转换它可能是。我猜这样做是为了使通行证的顺序变得不那么重要。

于 2010-05-25T13:37:59.917 回答