簡體   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