Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/sql-server-2008/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Optimization FPLs中的尾部调用优化是如何在汇编级别实现的?_Optimization_Assembly_Lisp_Tail Recursion_Ml - Fatal编程技术网

Optimization FPLs中的尾部调用优化是如何在汇编级别实现的?

Optimization FPLs中的尾部调用优化是如何在汇编级别实现的?,optimization,assembly,lisp,tail-recursion,ml,Optimization,Assembly,Lisp,Tail Recursion,Ml,LISPs或MLs如何实现尾部调用优化?我无法谈论不同编译器/解释器的具体实现细节,但一般来说尾部调用优化的操作如下: 通常,函数调用涉及如下内容: 为返回分配堆栈空间 将当前指令指针推到堆栈上 为函数参数分配堆栈空间并进行适当设置 调用你的函数 要返回它,请适当设置它的返回空间,弹出它应该返回的指令指针并跳转到它 然而,当一个函数处于尾部位置时,这几乎意味着您将返回您将要调用的函数的结果,您可能会很棘手,并且需要执行以下操作 将为自己的返回值分配的堆栈空间重新用作为要调用的函数的返回值分配的堆

LISPs或MLs如何实现尾部调用优化?

我无法谈论不同编译器/解释器的具体实现细节,但一般来说尾部调用优化的操作如下:

通常,函数调用涉及如下内容:

  • 为返回分配堆栈空间
  • 将当前指令指针推到堆栈上
  • 为函数参数分配堆栈空间并进行适当设置
  • 调用你的函数
  • 要返回它,请适当设置它的返回空间,弹出它应该返回的指令指针并跳转到它
  • 然而,当一个函数处于尾部位置时,这几乎意味着您将返回您将要调用的函数的结果,您可能会很棘手,并且需要执行以下操作

  • 将为自己的返回值分配的堆栈空间重新用作为要调用的函数的返回值分配的堆栈空间
  • 重新使用您应该返回的指令指针作为您将要调用的函数将使用的指令指针
  • 释放您自己的参数堆栈空间
  • 为参数分配空间并进行适当设置
  • 设置参数的值
  • 调用你的函数
  • 当它返回时,它将直接返回给您的呼叫方
  • 请注意#1和#2实际上不涉及任何工作,#3可能会很复杂或简单,具体取决于您的实现,而4-7不涉及您正在调用的函数的任何特殊内容。还请注意,所有这些都会导致相对于调用堆栈的堆栈增长为0,因此这允许infinte递归,并且通常会稍微加快速度


    还要注意,这种优化不仅可以应用于直接递归函数,还可以应用于共递归函数,事实上,所有处于尾部位置的调用。

    这取决于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
      x86\u 64
      风格,但在Win64上没有太大的不同)在寄存器中传递一定数量的参数,这使编译器在调用什么方面有很大的自由度,而无需在堆栈上传递任何内容。通过
      jmp
      进行的控制传输只会使尾部调用函数继承堆栈-包括最顶端的值,这将是
      do_a_tailcall
      的原始调用方的返回地址

    • SPARC不仅在寄存器中传递函数参数,还返回地址(它使用链接寄存器,
      %o7
      )。因此,当您通过
      调用
      传输控制时,实际上并不强制新的堆栈帧,因为它所做的只是设置链接寄存器和程序计数器。。。为了通过SPARC的另一个奇数功能撤销前者,所谓的延迟槽指令(用于
      mov%g1,%o7的
      或%g0,%g1,%o7
      -SPARC ish在
      调用之后,但在
      调用的目标到达之前执行)。上面的代码是从一个旧的编译器版本。。。而且没有理论上那么优化

    • ARM与SPARC类似,因为它使用链接寄存器,尾部递归函数只将未修改/未触及的消息传递给尾部调用。它也类似于x86,在尾部递归中使用
      b
      (分支)而不是“call”等价物(
      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