gfortran 支持尾调用消除吗?

2024-05-20

我编写了这个小程序来测试 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 or -O3 and -foptimize-sibling-calls。 那么,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 不需要bind(C)因为它实现了所有value类似于 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;

}

我不是 GIMPLE 专家,但是D.193x操作肯定链接到放置在堆栈上以供调用的临时表达式。

The PHI然后,操作会根据 if 语句中实际采用的分支来查找实际返回哪个版本的返回值(https://gcc.gnu.org/onlinedocs/gccint/SSA.html https://gcc.gnu.org/onlinedocs/gccint/SSA.html).

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

gfortran 支持尾调用消除吗? 的相关文章

  • 如何用好Fortran语句标签?

    我正在开发一个用 Fortran 95 编写的模型 我对此完全陌生 语句标签的概念似乎很奇怪 到目前为止我只找到了标签可以由作者任意决定的解释 通常以 10 为增量 除了更容易地找出语句的结尾位置之外 这些标签还有其他实际用途吗 以及关于如
  • Fortran 函数:指针作为实际参数,目标作为形式

    我正在尝试破译 Fortran 代码 它将指向函数的指针作为实际参数传递 而形式参数则是目标 它在主程序中定义并分配一个 globalDATA 类型的指针 然后调用一个传递该指针的函数 module dataGLOBAL type glob
  • fortran中双引号和单引号的区别?

    我刚刚开始使用 Fortran 对双引号和单引号的使用感到困惑 它们是等价的 它们的用法没有区别 您可以使用它来打印引号字符之一 print print 首先打印 进而 注意 您还可以在一行中使用两个引号字符来打印一个 print prin
  • 从 Fortran 字符串中提取单个字符

    我需要一个程序将基数 a 转换为基数 b 其中基数 a 和 b 可以是从 2 到 36 我的想法是使用字符串作为数字 作为中介转换为基数 10 然后从基数 10 转换为基数 b 由于我是 Fortran 新手 我不太理解函数和子字符串 现在
  • gfortran 未定义的引用

    我正在尝试编译一个依赖很多东西的程序 我使用并修改了提供的 makefile 来代表我的计算机设置 但在编译的最后一步中我不断收到许多未定义的引用 导致问题的命令行是 gfortran o cosmomc ParamNames o Matr
  • 如何将mortran代码转换为fortran代码

    我有一些 Mortran 代码 来自 glmnet 我想阅读和编译 我知道在编译时 Mortran首先转换为Fortran 然后编译 如果有预处理器的话 如何安装 Mortran 预处理器 特别是 OS X 上的 Mortran3 我在以下
  • 如何在Scala中实现尾递归快速排序

    我写了一个递归版本 def quickSort T xs List T p T T gt Boolean List T xs match case Nil gt Nil case gt val x xs head val left righ
  • 将数组从 .npy 文件读入 Fortran 90

    我使用 Python 以二维数组 例如 X 的形式生成一些初始数据 然后使用 Fortran 对它们进行一些计算 最初 当数组大小约为 10 000 x 10 000 时 np savetxt 在速度方面表现良好 但是一旦我开始增加数组的维
  • 在一条语句中对多个变量进行相同的赋值

    有没有一种方法可以为不同的变量分配相同的值 而无需在单个语句中构造数组 例如 如果我有变量a b c d and e 我可以分配类似的东西吗 a b c d e 10 0 我知道我可以用一行来做 a 10 0 b 10 0 c 10 0 d
  • 如何格式化整数以仅具有所需的大小?

    我一直在尝试以下代码 program hello write i9 10 end program hello 并改变格式字符串 尝试使写入输出的字符串大小恰好满足表示整数所需的大小 但到目前为止我无法管理它 如何在 Fortran 中编写
  • forrt1:严重(170):程序异常 - 堆栈溢出

    并提前感谢您的帮助 我已经编译了一个程序 不是我编写的 它在 Mac 上运行得很好 但是当我尝试在 Windows 上执行该程序时 在程序开始执行后不久 我收到以下错误消息 forrt1 严重 170 程序异常 堆栈溢出 我不是 ifort
  • 如何在 makefile 中拥有正确的 .mod 顺序

    我正在尝试用 Fortran 为我的项目创建一个 Makefile 并使其可在现在的项目中重用 我经过多次尝试后得出的 Mkefile 如下 问题是它在少数情况下工作正常 但现在我有这个文件 main f90 初始 f90 参数 f90 函
  • 带有过程参数的通用类型绑定过程

    我正在尝试编写一个通用的类型绑定过程 它将不同的回调函数作为参数 当编译以下代码 使用 ifort 12 1 3 时 我收到以下警告 module test type a type contains procedure t s gt at
  • 分发编译后的 fortran 库和模块文件

    我有一个Fortran使用很多模块的库 我用ifortWindows 上的编译器 因此 我得到一个 lib图书馆的文件和 mod所用模块的文件 这有一个缺点 我还必须分发 mod文件 如果我想在另一个程序中使用编译的库 如何防止这种情况发生
  • Fortran DLL 导入

    Fortran 中有一段代码罗伯特 L 帕克和菲利普 B 斯塔克 http www stat berkeley edu 7Estark Code sbvq f FORTRAN subroutine bv key m n a b bl bu
  • 将 FORTRAN 对象传递给 C,反之亦然

    我有我的 Fortran 对象 即 this object a this object b this object c 我想将它传递给用 C 编写的代码 我主要是一名 FORTRAN 程序员 而且我很少接触 C 我正在使用iso c bin
  • 派生类型数组:选择条目

    目前在我的代码中我有一个二维数组 integer allocatable elements 并定义一些常量 integer parameter TYP 1 integer parameter WIDTH 2 integer paramete
  • 如何在 Sublime Text 2 的 OSX 终端中显示构建结果

    我刚刚从 TextMate 切换到 Sublime Text 2 我非常喜欢它 让我困扰的一件事是默认的构建结果显示在 ST2 的底部 我的程序产生一些很长的结果 显示它的理想方式 如在 TM2 中 是并排查看它们 如何在 Mac 操作系统
  • Fortran 子例程返回错误值

    嘿 我正在开发一个 Fortran 程序 遇到了一个奇怪的问题 当我尝试在调用特定子例程之前直接输出数组的某些值时 我得到了正确的值 然后 我尝试在启动子例程时输出同一数组的一些值 它们都是 0 我最终在子例程之后输出数组的值 并且这些值回
  • 使用自制程序和安装程序安装 gfortran 是否会产生冲突?

    我正在按照在线教程使用 homebrew 安装一些 Python 模块 其中一个步骤是安装 gfortranbrew install gfortran 后来 我尝试使用另一个第三方安装脚本来安装一些Python模块 之后我意识到该脚本所做的

随机推荐