繼續搬運以前寫的東東
git:github.com/fw103699437…
FromNand2Tetris
lecture 8
一種面嚮物件語言(類似Java,名Jack)的方法呼叫過程。
背景知識:
Jack類似java,是一種執行在虛擬機器上的語言,如圖。
其中,local,argument等也是記憶體中的一塊區域,起始位置是由某個指標指定。如圖
方法呼叫的大致過程如圖:
那麼,不從棧的角度來看,而從位元組碼(因為jack與java太像,所以直接用java中的術語來表達)來看,方法呼叫到底是怎麼樣的呢?
具體過程
呼叫方法,需要call指令,被呼叫的方法,有一個function指令,方法執行結束,有一個return指令。
方法呼叫其實就是這三個指令。
思路:
呼叫方法者稱為caller,被呼叫方法稱為callee。
caller、callee分別有自己的local,argument,this,that等記憶體區域,他們分別有自己的起始位置。
我們想要進入方法之後能返回caller,那麼就必須儲存caller的local,argument等區域的起始位置,那麼儲存在哪裡呢?當然是儲存在棧裡,也就是上圖所說的saved frame裡,方法return之後,只要取出這些位置,然後再放到ram裡,就能恢復caller的執行,彷彿什麼事都沒發生過一樣(除了棧裡的引數變成了方法執行之後的結果)。
當然,還有一個問題,記憶體區域被重新指定之後,程式從第幾行重新執行,也就是return address,這個問題在call中再說。
call
call指令首先將returnaddress推入棧中,這個returnaddress是call指令結束之後的那個地址。(當然是結束之後,如果是當前位置的話,那結束之後就會再call一遍,迴圈了)
將local的位置儲存。
將argument的位置儲存。
將this的位置儲存。
將that的位置儲存。
將ARG重新設定為stack pointer -5 - 方法引數數量:呼叫方法時,首先得將引數推入棧頂,再呼叫call,又因為前面五步佔了五個位置,所以將argument重新設定為stack pointer -5 - 引數數量。
將local設定為stack pointer所在位置:因為區域性變數只在callee中有用,所以放在callee的記憶體區域中就行了。因為決定將區域性變數放在callee中記憶體開始的位置(方便),所以將local設定為stack pointer就好。
跳轉到functionname的位置:即在程式中找到functionname的位置,將program counter設定為那個位置,執行,也就是跳轉到了callee。
fuction
caller中的call執行完之後,就跳到了callee執行
首先給一個標誌 functionname,讓call中的goto能找到這裡。
然後重複n次(n=區域性變數數目),將0推入棧頂。
return
call中將caller的狀態儲存了下來,return的時候callee的上下文(context)並不需要儲存,所以只需要將caller中的上下文(context)恢復就可以了。
計算endFrame的位置,存入臨時變數(即找個安全的地方存下來)。
計算retAddr,存入臨時變數(這裡有一個問題要注意,如果arg的數目=0,那麼會覆蓋retAddr,所以retAddr要存入臨時變數)
將棧頂元素放入Arg所指位置:callee執行完之後,棧頂必須是要返回的值。(而且在Jack中,只能返回一個元素。)
將stack pointer設定為Arg+1:也就是對caller來說,方法執行結束,棧指標設定成正確的位置。
將endFrame-1所指向位置的值(即stack frame中儲存的上下文)賦給THAT。
將endFrame-2所指向位置的值(即stack frame中儲存的上下文)賦給THIS。
將endFrame-3所指向位置的值(即stack frame中儲存的上下文)賦給ARG。
將endFrame-4所指向位置的值(即stack frame中儲存的上下文)賦給LCL。
goto retAddr:由於call的時候,已經計算好了retAddr的位置(即call指令執行結束之後的位置),所以直接將program counter設定成retAddr,繼續執行即可。(也就是返回caller執行)
補充:
1:區域性變數數目,callee需要佔用的大小,都是在jack編譯至位元組碼的過程中就已經計算好了,有空再寫。
2:Jack是Jack,並不是Java,不可當作一樣,不過可以幫助理解。
3:對不執行在虛擬機器上的語言,方法呼叫其實也差不多,只要能將caller的狀態儲存下來,從callee中返回的時候能恢復如初就可以了。