繁体   English   中英

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

[英]How to implement tail calls in a custom VM

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

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

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

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

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是普通的流行音乐,只会丢弃堆栈中的前n个条目,但是POP_KEEP n是保留它们的变体。某处”(例如,在应用程序不能直接访问的辅助堆栈中,而只能由VM自己的机器访问-在讨论VM实现时,有时将具有这种字符的存储称为“寄存器”)和匹配的PUSH_KEPT n清空“寄存器” ”返回虚拟机的正常堆栈。

我认为您看错了这条路。 无需将旧变量从堆栈中弹出然后推入新变量,只需(小心地)重新分配已经存在的变量即可。 如果将代码重写为等效的迭代算法,则与执行优化时大致相同。

对于此代码:

 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;
} 

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM