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