gfortran是否支持尾部调用消除?

gfortran是否支持尾部调用消除?,fortran,gfortran,tail-recursion,Fortran,Gfortran,Tail Recursion,我制作这个小程序是为了测试gfortran是否进行尾部调用消除: program tailrec implicit none print *, tailrecsum(5, 0) contains recursive function tailrecsum (x, running_total) result (ret_val) integer, intent(in) :: x integer, intent(in) :: running_total integer

我制作这个小程序是为了测试gfortran是否进行尾部调用消除:

program tailrec
implicit none

print *, tailrecsum(5, 0)

contains

recursive function tailrecsum (x, running_total) result (ret_val)
    integer, intent(in) :: x
    integer, intent(in) :: running_total
    integer             :: ret_val

    if (x == 0) then
        ret_val = running_total
        return
    end if
    ret_val = tailrecsum (x-1, running_total + x)
end function tailrecsum

end program
为了检查,我使用-S选项编译了它,以查看说明。这里是tailrecsum函数的行:

tailrecsum.3429:
.LFB1:
    .cfi_startproc
    movl    (%rdi), %eax
    testl   %eax, %eax
    jne .L2
    movl    (%rsi), %eax
    ret
    .p2align 4,,10
    .p2align 3
.L2:
    subq    $24, %rsp
    .cfi_def_cfa_offset 32
    leal    -1(%rax), %edx
    addl    (%rsi), %eax
    leaq    8(%rsp), %rdi
    leaq    12(%rsp), %rsi
    movl    %edx, 8(%rsp)
    movl    %eax, 12(%rsp)
    call    tailrecsum.3429
    addq    $24, %rsp
    .cfi_def_cfa_offset 8
    ret
    .cfi_endproc
最后,我看到
call tailrecsum.3429
,因此认为没有尾部调用消除。当我使用
-O2
-O3
-fo优化兄弟调用时,情况也是如此。

那么,gfortran是否不支持这一点,或者这是我的代码的问题?

它确实支持这一点。要避免许多损害尾部调用优化的非常微妙的陷阱是相当棘手的


如果按值传递参数,编译器优化尾部调用会变得更简单。在这种情况下,接收过程不需要有一个指针(地址)指向任何临时对象

事实上,此修改足以消除尾部调用并启用无限递归:

recursive function tailrecsum (x, running_total) result (ret_val) bind(C)
    integer, value :: x
    integer, value :: running_total
    integer             :: ret_val

    if (x == 0) then
        ret_val = running_total
        return
    end if
    ret_val = tailrecsum (x-1, running_total + x)
end function tailrecsum
Gfortran不需要绑定(C)
,因为它将所有
实现为类C传递值。英特尔确实需要它,因为它会创建一个临时地址并传递其地址

细节可能因不同的体系结构而异,具体取决于谁负责清理哪些内容


考虑这个版本:

program tailrec
use iso_fortran_env

implicit none

integer(int64) :: acc, x

acc = 0

x = 500000000

call tailrecsum(x, acc)

print *, acc

contains

recursive subroutine tailrecsum (x, running_total)
    integer(int64), intent(inout) :: x
    integer(int64), intent(inout) :: running_total
    integer(int64)             :: ret_val

    if (x == 0)  return
    
    running_total = running_total + x
    x = x - 1
    call tailrecsum (x, running_total)
end subroutine tailrecsum



end program
在500000000次迭代中,它显然会在没有TCO的情况下毁掉整个堆栈,但它不会:

> gfortran -O2 -frecursive tailrec.f90 
> ./a.out 
   125000000250000000
您可以使用
-fdump tree optimized
更轻松地检查编译器的功能。老实说,我甚至没有费心去理解你的汇编输出。X86汇编对我来说太深奥了,我简单的大脑只能处理某些RISC

您可以看到,在调用原始版本中的下一个迭代之后,仍有很多工作要做:

  <bb 6>:
  _25 = _5 + -3;
  D.1931 = _25;
  _27 = _18 + _20;
  D.1930 = _27;
  ret_val_28 = tailrecsum (&D.1931, &D.1930);
  D.1930 ={v} {CLOBBER};
  D.1931 ={v} {CLOBBER};

  <bb 7>:
  # _29 = PHI <_20(5), ret_val_28(6)>

  <bb 8>:
  # _22 = PHI <_11(4), _29(7)>

  <bb 9>:
  # _1 = PHI <ret_val_7(3), _22(8)>
  return _1;

}
:
_25 = _5 + -3;
D.1931=_25;
_27 = _18 + _20;
D.1930=_27;
ret_val_28=尾收入(1931年和1930年);
D.1930={v}{CLOBBER};
D.1931={v}{CLOBBER};
:
#_29=φ
:
#_22=φ
:
#_1=φ
返回1;
}
我不是GIMPLE方面的专家,但是
D.193x
操作肯定链接到为调用而放在堆栈上的临时表达式

然后,
PHI
操作根据if语句()中实际执行的分支查找实际返回的返回值的版本


正如我所说的,有时将代码简化为gfortran可以接受的正确形式来执行尾部调用优化是很困难的。

谢谢!我正在做更多的实验,并且
gfortran-fooptimizesiblingcalls tailrec.f90
不足以让它工作(->segfaults)<代码>-O1也不起作用。查看
-O2
版本的汇编,整个函数似乎已经内联到main。。。okai,
-O1-fooptimize sibling calls
正在工作!是的,它是内联的,但您可以单独编译它,您会看到它充满了GOTO,但它不包含任何调用。Okai,现在我想知道为什么使用
-fooptimize sibling calls
是不够的。所以,我现在正在尝试
-O1
附带的不同选项组合…奇怪的是:使用
-O1-fooptimize sibling calls
工作,但是
-fooptimize sibling calls
,然后所有
-O1
选项都不会显式执行。我认为
-fooptimize sibling calls
仅用于间接递归。