[英]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.