使用clang -O2(或使用online demo)编译下一段代码后:
#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])));
}
我正在flip
中获取llvm程序集的下一个片段:
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)
可以用sombody解释为什么clang生成这样的代码?
我也无法理解为什么llvm有tail call
,如果它只是检查下一个ret
是否会使用prev call
的结果,然后进行适当的优化或生成本机等效的尾调指令?
答案 0 :(得分:3)
查看'call' instruction中的LLVM Assembly Language Reference Manual。它说:
可选的“tail”标记表示被调用者函数不访问调用者中的任何allocas或varargs。请注意,即使在转发指令之前没有发生呼叫,也可能将呼叫标记为“尾部”。
可能是Clang中的一个LLVM优化传递分析了被调用者是否访问调用者中的任何allocas或varargs。如果没有,则传递将调用标记为尾调用,并让LLVM的另一部分找出如何处理“尾部”标记。也许这个函数现在不能成为一个真正的尾部调用,但是在进一步转换后它可能是。我猜这样做是为了使通行证的排序不那么重要。