为什么gcc为一个版本执行尾调用优化而不为另一个版本执行尾调用优化?

时间:2017-10-25 11:51:09

标签: c++ gcc optimization tail-call-optimization

尝试尾调用优化(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

1 个答案:

答案 0 :(得分:2)

fact2()中,完成递归后,需要从unsigned long long intunsigned 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