我做了这个小程序来测试,如果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
和-foptimize-sibling-calls
时也是如此。
那么,gfortran不支持这个还是我的代码问题?
答案 0 :(得分:2)
确实支持它。避免许多非常微妙的陷阱会损害尾部调用优化是非常棘手的。
考虑这个版本:
> gfortran -O2 -frecursive tailrec.f90
> ./a.out
125000000250000000
使用5000000次迭代,它会在没有TCO的情况下明显地破坏堆栈,但它不会:
-fdump-tree-optimized
您可以使用 <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;
}
更轻松地检查编译器的功能。老实说,我甚至懒得试图理解你的装配输出。 X86程序集对我来说太神秘了,我的简单大脑只能处理某些RISC。
您可以看到在原始版本中调用下一次迭代后仍有很多事情发生:
D.193x
我不是GIMPLE的专家,但是PHI
操作肯定与放置在调用的堆栈上的临时表达式相关联。
business_day,commodity,total,delivery,total_lots
.
.
20160831,CTC,,201710,10
20160831,CTC,,201711,10
20160831,CTC,,201712,10
20160831,CTC,Total,,385
20160831,HTC,,201701,30
20160831,HTC,,201702,30
.
.
操作然后根据if语句中实际采用的分支(https://gcc.gnu.org/onlinedocs/gccint/SSA.html)找到实际返回的返回值版本。
正如我所说,将代码简化为正确的形式有时很难将gfortran执行尾调用优化。