尝试尾调用优化(tco),我偶然发现了以下奇怪的例子:
unsigned long long int fac1(unsigned long long int n){
if (n==0)
return 1;
return n*fac1(n-1);
}
实际上,我印象深刻,gcc was able在这里执行tco(带-O2
标志),因为它不是那么直接:
fac1(unsigned long long):
testq %rdi, %rdi
movl $1, %eax
je .L4
.L3:
imulq %rdi, %rax
subq $1, %rdi
jne .L3
rep ret
.L4:
rep ret
但是,在将返回类型从unsigned long long int
更改为unsigned int
之后,gcc无法执行tlo:
unsigned int fac2(unsigned long long int n){
if (n==0)
return 1;
return n*fac2(n-1);
}
我们可以清楚地看到resulting assembly中的递归调用:
fac2(unsigned long long):
testq %rdi, %rdi
jne .L16
movl $1, %eax
ret
.L16:
pushq %rbx
movq %rdi, %rbx
leaq -1(%rdi), %rdi
call fac2(unsigned long long)
imull %ebx, %eax
popq %rbx
ret
起初,我认为这是错过的优化,但现在我不确定,因为clang isn't able也可以执行此优化。所以也许我不知道哪种语言可以防止这种优化。
为什么gcc不对fac2
函数执行尾调用优化,只针对fac1
?
我很清楚,在第二个版本中,必须降低结果。显然这是唯一的区别。但为什么这是一个问题并阻止tlo?
例如,如果我帮助编译器并将我的函数重写为经典的尾递归(它应该产生与版本fac2
相同的结果):
unsigned int tlo_fac(unsigned long long int n, unsigned long long int cur){
if (n==0)
return cur;
return tlo_fac(n-1, n*cur);
}
unsigned int fac(unsigned long long int n){
return tlo_fac(n,1);
}
我得到了一个tlo优化版本identical to fac1
(高32位是allowed to contain garbage,因此在内联后可以使用imulq
:
fac(unsigned long long):
testq %rdi, %rdi
movl $1, %eax
je .L10
.L11:
imulq %rdi, %rax
subq $1, %rdi
jne .L11
.L10:
rep ret
答案 0 :(得分:2)
在fact2()
中,完成递归后,需要从unsigned long long int
到unsigned int
unsigned int fac2(unsigned int n)
生成以下程序集,
fac2(unsigned int):
testl %edi, %edi
movl $1, %eax
je .L10
.L9:
imull %edi, %eax
subl $1, %edi
jne .L9
rep ret
.L10:
rep ret