Optimization FPLs中的尾部调用优化是如何在汇编级别实现的?
LISPs或MLs如何实现尾部调用优化?我无法谈论不同编译器/解释器的具体实现细节,但一般来说尾部调用优化的操作如下: 通常,函数调用涉及如下内容:Optimization FPLs中的尾部调用优化是如何在汇编级别实现的?,optimization,assembly,lisp,tail-recursion,ml,Optimization,Assembly,Lisp,Tail Recursion,Ml,LISPs或MLs如何实现尾部调用优化?我无法谈论不同编译器/解释器的具体实现细节,但一般来说尾部调用优化的操作如下: 通常,函数调用涉及如下内容: 为返回分配堆栈空间 将当前指令指针推到堆栈上 为函数参数分配堆栈空间并进行适当设置 调用你的函数 要返回它,请适当设置它的返回空间,弹出它应该返回的指令指针并跳转到它 然而,当一个函数处于尾部位置时,这几乎意味着您将返回您将要调用的函数的结果,您可能会很棘手,并且需要执行以下操作 将为自己的返回值分配的堆栈空间重新用作为要调用的函数的返回值分配的堆
还要注意,这种优化不仅可以应用于直接递归函数,还可以应用于共递归函数,事实上,所有处于尾部位置的调用。这取决于CPU体系结构和/或操作系统,哪些函数可以进行尾部调用优化。这是因为CPU和/或操作系统之间的调用约定(用于传递函数参数和/或在函数之间传输控制)不同。它通常归结为尾部调用中传递的任何内容是否必须来自堆栈。以如下函数为例:
void do_a_tailcall(char *message)
{
printf("Doing a tailcall here; you said: %s\n", message);
}
如果在32位x86(Linux)上编译此函数,即使进行了高度优化(-O8-fomit frame pointer
),您也会得到:即,一个经典函数,带有stackframe setup/teardown(subl$12,%esp
/addl$12,%esp
)和函数中的显式ret
在64位x86(Linux)中,这看起来像:
do_a_tailcall:
movq %rdi, %rsi
xorl %eax, %eax
movl $.LC0, %edi
jmp printf
.LC0:
.string "Doing a tailcall here; you said: %s\n"
所以它的尾巴被优化了
在一种完全不同类型的CPU体系结构(SPARC)上,这看起来像(我在中留下了编译器的注释):
还有一个。。。ARM(Linux EABI):
这里的区别在于参数的传递方式和控制权的转移:
- 32位x86(
/stdcall
类型调用)在堆栈上传递参数,因此尾部调用优化的可能性非常有限-除了特定的情况外,只有在精确的参数传递或尾部调用根本不带参数的函数时才可能发生这种情况cdecl
- 64位x86(UNIX
风格,但在Win64上没有太大的不同)在寄存器中传递一定数量的参数,这使编译器在调用什么方面有很大的自由度,而无需在堆栈上传递任何内容。通过x86\u 64
进行的控制传输只会使尾部调用函数继承堆栈-包括最顶端的值,这将是jmp
的原始调用方的返回地址do_a_tailcall
- SPARC不仅在寄存器中传递函数参数,还返回地址(它使用链接寄存器,
)。因此,当您通过%o7
传输控制时,实际上并不强制新的堆栈帧,因为它所做的只是设置链接寄存器和程序计数器。。。为了通过SPARC的另一个奇数功能撤销前者,所谓的延迟槽指令(用于调用
mov%g1,%o7的
-SPARC ish在或%g0,%g1,%o7
调用之后,但在
调用的目标到达之前执行)。上面的代码是从一个旧的编译器版本。。。而且没有理论上那么优化
- ARM与SPARC类似,因为它使用链接寄存器,尾部递归函数只将未修改/未触及的消息传递给尾部调用。它也类似于x86,在尾部递归中使用
(分支)而不是“call”等价物(b
,分支和链接)bl
在所有架构中,寄存器中至少可以发生一些参数传递,编译器可以对大量函数应用尾部调用优化。在询问之前,您对此做过任何研究吗?一个开始寻找的地方。你也可以在sicp的书中读到,
do_a_tailcall:
movq %rdi, %rsi
xorl %eax, %eax
movl $.LC0, %edi
jmp printf
.LC0:
.string "Doing a tailcall here; you said: %s\n"
.L16:
.ascii "Doing a tailcall here; you said: %s\n\000"
!
! SUBROUTINE do_a_tailcall
!
.global do_a_tailcall
do_a_tailcall:
sethi %hi(.L16),%o5
or %g0,%o0,%o1
add %o5,%lo(.L16),%o0
or %g0,%o7,%g1
call printf ! params = %o0 %o1 ! Result = ! (tail call)
or %g0,%g1,%o7
.LC0:
.ascii "Doing a tailcall here; you said: %s\012\000"
do_a_tailcall:
@ args = 0, pretend = 0, frame = 0
@ frame_needed = 0, uses_anonymous_args = 0
@ link register save eliminated.
mov r1, r0
movw r0, #:lower16:.LC0
movt r0, #:upper16:.LC0
b printf