gfortran是否支持尾部调用消除?
我制作这个小程序是为了测试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
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
仅用于间接递归。