如何在自定义虚拟机中实现尾调用
如何在自定义虚拟机中实现尾调用?
我知道我需要弹出原始函数的本地堆栈,然后弹出它的参数,然后推送新参数。但是,如果我弹出函数的本地堆栈,我该如何推送新参数呢?它们刚刚从堆栈中弹出。
How can I implement tail calls in a custom virtual machine?
I know that I need to pop off the original function's local stack, then it's arguments, then push on the new arguments. But, if I pop off the function's local stack, how am I supposed to push on the new arguments? They've just been popped off the stack.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我想当然地认为我们在这里讨论的是传统的“基于堆栈”的虚拟机。
您弹出当前函数的本地堆栈在非堆栈“寄存器”中保留仍然相关的部分(其中“相关部分”显然是即将进行的递归尾调用的参数),然后(一旦清除了函数的所有本地堆栈和参数),您就可以推送递归调用的参数。例如,假设您正在优化的函数类似于:
如果没有优化,它可能会产生类似于符号的字节码:
CALL_FUN2 表示“调用带有两个参数的函数”。通过优化,它有时可能会变成这样:
当然,我正在编写符号字节码,但我希望意图很明确:
POP_DISCARD n
是正常的弹出,只是丢弃堆栈中的 topn
条目,但POP_KEEP n
是一种变体,将它们保留在“某处”(例如,在应用程序无法直接访问的辅助堆栈中,而只能由 VM 访问)自己的机器——在讨论 VM 实现时,具有此类字符的存储有时称为“寄存器”)和匹配的 PUSH_KEPT n ,它将“寄存器”清空回 VM 的正常堆栈。I take it for granted that we're discussing a traditional "stack-based" virtual machine here.
You pop off the current function's local stack preserving the still-relevant parts in non-stack "registers" (where the "relevant parts" are, clearly, the argument for the forthcoming recursive tail call), then (once all of the function's local stack and arguments are cleaned up) you push the arguments for the recursive call. E.g., suppose the function you're optimizing is something like:
which without optimization might produce byte-code symbolically like:
the CALL_FUN2 means "call a function with two arguments". With the optimization, it could become sometime like:
Of course I'm making up my symbolic bytecodes as I go along, but I hope the intent is clear:
POP_DISCARD n
is the normal pop that just discards the topn
entries from the stack, butPOP_KEEP n
is a variant that keeps them "somewhere" (e.g. in an auxiliary stack not directly accessible to the application but only to the VM's own machinery -- storage with such a character is sometimes called "a register" when discussing VM implementation) and a matchingPUSH_KEPT n
which empties the "registers" back into the VM's normal stack.我认为你看待这个问题的方式是错误的。不要将旧变量从堆栈中弹出,然后推送新变量,只需重新分配已经存在的变量(小心地)即可。如果您将代码重写为等效的迭代算法,这与会发生的优化大致相同。
对于此代码:
将不需要
在堆栈上弹出或压入任何内容,只需重新分配即可。
显然,这可以进一步优化,将退出条件放在第二位,允许我们跳过跳转,从而减少操作。
再看一遍,这个“程序集”更好地反映了这个C++,它显然避免了递归调用
I think you're looking at this the wrong way. Instead of popping the old variables off the stack and then pushing the new ones, simply reassign the ones already there (carefully). This is roughly the same optimization that would happen if you rewrote the code to be the equivalent iterative algorithm.
For this code:
would be
There's no need to pop or push anything on the stack, merely reassign.
Clearly, this can be further optimized, by putting the exit condition second, allowing us to skip a jump, resulting in fewer operations.
Looking again, this "assembly" better reflects this C++, which clearly has avoided the recursion calls