5.1.3 子程式
5.1.3 子程式
當設計一個執行運算的機器時,我們優先設計能被運算的不同的部分共享的元件,而不是單獨的元件。
考慮這種情況:一個機器有兩個求最大公約數的運算,一個是找到暫存器A,B的內容的最大公約數,
另一個是找到暫存器C,D的內容的最大公約數。開始時我們可能設計出一個原生的求最大公約數的操作,
然後擴充套件為用最原生的操作來生成 求最大公約數的兩個例項。在圖5.7中,僅僅顯示出了機器的資料路徑的
求最大公約數的部分,沒有顯示出它們如何連線到機器的其它部分。圖中也顯示了機器的控制序列的
相應部分。
gcd-1
(test (op =) (reg b) (const 0))
(branch (label after-gcd-1))
(assign t (op rem) (reg a) (reg b))
(assign a (reg b))
(assign b (reg t))
(goto (label gcd-1))
after-gcd-1
.
.
gcd-2
(test (op =) (reg d) (const 0))
(branch (label after-gcd-2))
(assign s (op rem) (reg c) (reg d))
(assign c (reg d))
(assign d (reg s))
(goto (label gcd-2))
after-gcd-2
圖5.7 對一個GCD機器中有兩個GCD計算的資料路徑和控制器序列的部分
這個機器有兩個求餘數的操作盒子和兩個測試相等性的盒子。如果
重複的元件是複雜的,正如餘數盒子,在構建時這將不是經濟的方式。
為了GCD計算,通過使用相同的元件,我們能夠避免重複的資料路徑元件,
這麼做不影響更大規模的機器的計算的其它部分。如果在暫存器a,b中的值
在控制器取gcd-2的時候不再需要了(或者是這些值能被移動到其它的
暫存器中儲存起來),我們能修改機器讓它來使用暫存器a,b而不是暫存器c,d
在計算第二個GCD像第一個一樣。如果我們這麼做,我們得到了控制器序列如圖5.8
我們已經移除了重複的資料路徑元件,但是現在控制器有兩個GCD序列它們只有
入口點的標籤不同。如果通過對一個單獨的序列進行分支,也就是一個GCD的子程式
在它的結尾處我們把分支回到主指令序列的正確的位置,以這種方法來代替這兩個序列
就更好了。我們完成這個任務如下:在分支到GCD之前,我們把一個特定的值(例如0或者1)
放入一個特定的暫存器continue中。在GCD子程式的結尾處,我們返回到after-gcd-1 或者
after-gcd-2 ,這依賴於暫存器continue的值。圖5.9顯示了這個結果的序列的相關的部分,
它僅包括了gcd指令的一個拷貝。
gcd-1
(test (op =) (reg b) (const 0))
(branch (label after-gcd-1))
(assign t (op rem) (reg a) (reg b))
(assign a (reg b))
(assign b (reg t))
(goto (label gcd-1))
after-gcd-1
.
.
gcd-2
(test (op =) (reg b) (const 0))
(branch (label after-gcd-2))
(assign t (op rem) (reg a) (reg b))
(assign a (reg b))
(assign b (reg t))
(goto (label gcd-2))
after-gcd-2
圖5.8 對於有兩個不同的GCD計算的並且使用相同的資料路徑元件的機器而言
控制器序列的部分
gcd
(test (op =) (reg b) (const 0))
(branch (label gcd-done))
(assign t (op rem) (reg a) (reg b))
(assign a (reg b))
(assign b (reg t))
(goto (label gcd))
gcd-done
(test (op =) (reg continue) (const 0))
(branch (label after-gcd-1))
(goto (label after-gcd-2))
....
(assign continue (const 0))
(goto (label gcd))
after-gcd-1
....
(assign continue (const 1))
(goto (label gcd))
after-gcd-2
圖5.9 使用一個continue暫存器來避免圖5.8中的重複的控制器序列
gcd
(test (op =) (reg b) (const 0))
(branch (label gcd-done))
(assign t (op rem) (reg a) (reg b))
(assign a (reg b))
(assign b (reg t))
(goto (label gcd))
gcd-done
(goto (reg contiue))
....
(assign continue (label after-gcd-1))
(goto (label gcd))
after-gcd-1
....
(assign continue (label after-gcd-2))
(goto (label gcd))
after-gcd-2
圖5.10 把標籤的值賦給continue暫存器,簡化並且生成了圖5.9的策略。
在處理小問題時,這是合理的方法,但是,如果在控制器序列中有許多的GCD計算的例項,這就是
有點難了。在GCD的子程式之後,我們要確定繼續執行哪的指令,我們將需要測試在資料路徑
和在控制器中使用GCD的所有的地方的分支指令。為了實現子程式的一個更強有力的方法是讓繼續的暫存器
在控制器的序列中儲存入口點的標籤,當子程式完成後,就執行暫存器中指定的地方。實現這個策略需要在資料路徑和暫存器機器中的控制器之間有一個新型的連線:這必須是一種方式在控制器序列中為一個暫存器
賦值一個標籤,並且這個標籤值能被從暫存器中取出來,並且作為指定的入口點來繼續執行。
為了反映這種能力,我們將擴充套件暫存器機器的語言的賦值指令,讓它可以把控制器序列中的標籤的值賦給一個暫存器。我們也擴充套件了goto指令,允許執行的入口點被描述為一個暫存器的值而不僅僅是一個常數的標籤所描述的入口點。使用這個新的組裝,我們能中止gcd子程式,以儲存在繼續的暫存器中的位置的分支。
這導致了圖5.10中顯示的控制器的序列。
有多於一個子程式的機器,能使用多個繼續的暫存器(例如,gcd-continue,factorial-continue)或者
我們有多個子程式共享一個繼續的暫存器。共享是經濟的,但是我們必須很小心,如果我們有一個子程式sub1,它呼叫了另一個子程式sub2. 為了呼叫sub2,而設定continue之前,如果我們沒有在sub1中把continue的值儲存到其它的暫存器中,sub1將在它自己完成時不知道去哪裡。在下一部分中開發的機制處理了遞迴,也提供了
巢狀的子程式呼叫這個問題的更好的解決方案。