如何在自定义VM中实现尾调用

时间:2010-05-13 14:10:10

标签: c++ vm-implementation tail-call

如何在自定义虚拟机中实现尾调用?

我知道我需要弹出原始函数的本地堆栈,然后是它的参数,然后推送新的参数。但是,如果我弹出函数的本地堆栈,我该如何推动新的参数呢?他们刚从堆栈中弹出。

2 个答案:

答案 0 :(得分:4)

我认为我们在这里讨论传统的“基于堆栈”的虚拟机是理所当然的。

弹出当前函数的本地堆栈,保留非堆栈“寄存器”中仍然相关的部分(其中“相关部分”显然是即将发生的递归尾调用的参数),然后(一旦所有函数的本地堆栈和参数被清理),你推送递归调用的参数。例如,假设您正在优化的功能类似于:

def aux(n, tot):
  if n <= 1: return tot
  return aux(n-1, tot * n)
没有优化的

可能会产生象征性的字节码:

AUX:   LOAD_VAR   N
       LOAD_CONST 1
       COMPARE
       JUMPIF_GT  LAB
       LOAD_VAR   TOT
       RETURN_VAL
LAB:   LOAD_VAR   N
       LOAD_CONST 1
       SUBTRACT
       LOAD_VAR   TOT
       LOAD_VAR   N
       MULTIPLY
       CALL_FUN2  AUX
       RETURN_VAL

CALL_FUN2表示“调用具有两个参数的函数”。通过优化,它可能会变得像:

   POP_KEEP     2
   POP_DISCARD  2
   PUSH_KEPT    2
   JUMP         AUX

当然,我正在编写符号字节码,但我希望意图明确:POP_DISCARD n是普通的pop,它只丢弃堆栈中的顶级n条目,但是POP_KEEP n是一种变体,它将它们“保持在某个地方”(例如,在一个不能直接访问应用程序但只能访问VM自己的机器的辅助堆栈中 - 具有这种字符的存储在讨论时有时称为“寄存器” VM实现)和匹配的PUSH_KEPT n,它将“寄存器”清空回VM的正常堆栈。

答案 1 :(得分:1)

我认为你正在以错误的方式看待这个问题。不要将旧变量从堆栈中弹出然后推出新变量,而只需重新分配已存在的变量(小心)。如果您将代码重写为等效的迭代算法,这与大致相同的优化。

对于此代码:

 int fact(int x, int total=1) {
     if (x == 1)
         return total;
     return fact(x-1, total*x);
 }

将是

 fact:
   jmpne x, 1, fact_cont  # if x!=1 jump to multiply
   retrn total            # return total
 fact_cont:               # update variables for "recursion
   mul total,x,total      # total=total*x
   sub x,1,x              # x=x-1
   jmp fact               #"recurse"

没有必要弹出或推动堆叠上的任何东西,只需重新分配 显然,这可以通过将退出条件置于第二位进一步优化,允许我们跳过跳转,从而减少操作。

 fact_cont:               # update variables for "recursion
   mul total,x,total      # total=total*x
   sub x,1,x              # x=x-1
 fact:
   jmpne x, 1, fact_cont  # if x!=1 jump to multiply
   retrn total            # return total

再看一下,这个“程序集”更好地反映了这个C ++,它显然避免了递归调用

int fact(int x, int total=1)
    for( ; x>1; --x)
        total*=x;
    return total;
}