處理器設計所圍繞的兩個問題: 指令集設計(軟體) 機器結構設計(硬體) 第二章為指令集設計,第三章為機器結構設計。 指令集設計所受影響: 程式語言(精度,定址模式,跳轉指令) 應用(科學計算 -> 應用) 操作系統(記憶體管理,程式的不連續性) 指令集如何設計: 早期,硬體可否實現 -> 如今, 是否 ...
處理器設計所圍繞的兩個問題:
指令集設計(軟體)
機器結構設計(硬體)
第二章為指令集設計,第三章為機器結構設計。
指令集設計所受影響:
程式語言(精度,定址模式,跳轉指令)
應用(科學計算 -> 應用)
操作系統(記憶體管理,程式的不連續性)
指令集如何設計:
早期,硬體可否實現 -> 如今, 是否實際有用。
*能否有利於高級語言編寫高效而緊湊的程式。-----------一
要優雅,體繫結構的選擇------------------------------------二
一:指令集與高級語言
高級語言的功能集:
表達式和賦值語句-------------------------------------------(一)
高級數據抽象-------------------------------------------------(二)
條件語句和迴圈----------------------------------------------(三)
編譯函數調用-------------------------------------------------(四)
(一)表達式和賦值語句
a = b + c;
add a, b, c;高級語言(上)都可以映射為指令(下),稱為雙操作數指令(兩個操作數產生一個結果),也稱為三操作數指令(兩個源操作數和一個目的操作數)
所有的算數/邏輯運算來說,操作數都在處理器的寄存器中:
1、算數/邏輯運算用的ALU在處理器中,位置更接近。
2、操作數的可定址性,如果操作數在記憶體中,隨著記憶體大小的增加,記憶體的唯一定址的地址大小也增加,指令大小也會增加。
寄存器數量必須較小,以限制定址的位數,並引入定址模式,寄存器定址。
將操作數從記憶體搬到寄存器需要兩條指令:
載入:ld r2, b; r2 <- b
存儲:st r1, a; a <- r1
將操作數搬到寄存器而不是直接使用記憶體操作數的好處:重用速度快(d=a*b+a*c+b*c;)
為了防止在從記憶體搬運的地址操作數的長度不受操作數可定址性的影響,引入基址加偏移量定址模式。
ld r2, offset(rb); r2 <- MEM[rb + offset]
約定,一個字32位,半字16位,位元組8位
操作數寬度與其精度有關:
精度越低記憶體中占用空間就越小,在算數/邏輯運算時還有一定的時間優勢。
操作數精度最好恰好滿足類型需求。
指令集應該包含不同精度操作數:
ld r1, offset(rb); 從地址offset+rb處裝一個字到r1中
ldb r1, offset(rb); 從地址offset+rb處裝一個位元組到r1中
可定址性:與前面的不同,這裡的可定址性指的是記憶體中能被定址的最小精度,因為不可能一位一位定址。
位元組序,一個字中的各個位元組排列的順序:
0x11223344
大端模式,高位在小地址
小端模式,高位在大地址
聲明某種數據類型,不要用別的精度訪問,因為位元組序。在網路有關代碼需要在不同機器上使用格式轉換來回切換。
程式在記憶體中占據的空間被稱為記憶體印記。
如果數據結構中有多種不同精度的變數,打包很有意義,保證沒有空間浪費。
例:struct {char a;char b[3];}可能為這種佈局
浪費了50%的存儲空間,有效的編譯器會將這種情況打包為
上面除了節省空間外,還能減少處理器和記憶體之間的訪問次數,非常高效。
但打包並非總是正確的途徑:
struct {char a; int b;} char一個位元組,int四個位元組,可能為
lsb為低位,msb為高位
上面這種非常低效,為了讀取int,需要訪問兩次,所以往往要求操作數從可定址地址開始。這就是對齊限制。
所以編譯器會使用
儘管浪費了37.5%空間,但變得更加高效了。
綜上說明瞭高級語言的賦值與表達式轉換成指令集後在記憶體中的位置,大小以及排序方式和打包。
(二)高級數據抽象
除了標量意外,高級語言支持的數據抽象(數組,結構)因為龐大的體積,處理器中的寄存器數量不足以支持,只能分配在記憶體里。
高級語言的結構數據類型,可以通過基址加偏移量的定址模式執行。
許多編程語言允許數組動態決定大小,因為不知道數組所需要的存儲空間,所以記憶體中可能並非線性存儲。
(三)條件語句和迴圈
if-then-else語句
引入例子beq r1, r2, offset:
1)比較r1和r2
2)如果相等,下一條指令地址為PC+offsetadjusted。(PC為程式計數器當前地址,程式計數器相對定址)
3)如果不等,下一條指令為跟在beq後面的指令。
條件語句受偏移量大小的限制,8位的偏移量大小隻能在(PC-128,PC+127)範圍,所以需要引入無條件跳轉:
J rtraget。
switch語句
如果case的數量有限,最好就是編譯為多個if-then-else語句結構。
如果有許多連續的,不稀疏的case,if-then-else就低效。
另一種選擇就是用跳轉表記錄所有case代碼段的起始地址,靠無條件跳轉實現。
迴圈語句
高級語言的迴圈例子:
j = 0;
loop: b = b + a[j];
j = j + 1;
if (j != 100) go to loop;
假設寄存器r1用於保存變數j,而寄存器r2包含值100,則轉換為
loop: ...
...
...
beq r1, r2, loop
不需要新指令和定址模式。
其他的迴圈(for,while)都能用上述的條件或者非條件分支編譯。
(四)編譯函數調用
函數調用的過程:
在main函數中調用了函數foo。控制流轉移到該函數的入口處。
退出foo時,控制流返回main函數中緊接著goo函數的語句。
引入術語:
調用者caller,做出過程調用的實體。
被調用者callee,被調用的實體。
上述例子中調用者是main函數,被調用者是foo函數。
當調用者調用被調用者時,調用者的寄存器的內容是需要快速保存和恢復,解決方案有硬體和軟體兩種。
硬體:
使用影子寄存器,用來保存
只有最左邊的寄存器是可見的。
特點:快,但會受到嵌套層次的限制。
軟體:
將調用點狀態保存入棧中,因為棧具有後進先出的特性,滿足函數調用需求。
編譯器會選擇處理器中一個寄存器作為專用的棧指針,非必需,但方便。
特點:記憶體和處理器之間搬運數據慢,不受嵌套層次限制。
縮小性能差距方法(不保存全部寄存器狀態):
調用者獲得全部寄存器的一個子集(s寄存器組)隨意使用。
被調用者如果要用s寄存器組則需要保存/恢復他們,不用則不考慮。
還有一部分寄存器的子集(t寄存器組),調用者和被調用者可以共用且不需要保存/恢復。
大致流程:
1)參數傳遞,參數個數超出寄存器限制,用棧來傳遞多餘的參數。
2)記錄返回地址,引入 JAL rtarget , rlink :
返回地址保存在rlink 寄存器中,將PC置為rtarget
3)將控制權交給被調用者。
4)被調用者局部變數的空間。
5)返回值,返回值超出寄存器保存範圍,用棧來傳遞。
6)返回道調用點J rlink
軟體慣例:
活動記錄:
棧中與當前執行的過程有關的一部分區域。
遞歸:
無需在指令集體繫結構上增加任何東西就能支持遞歸,棧機制保證了每一個實例。
幀指針:
儘管可以跟蹤棧指針的移動,但是維護和時間代價大。
幀指針包含當前函數的活動記錄的第一個地址,且在過程執行時不會改變。
如果調用其他過程,被調用者需要將幀指針保存到棧,並將當前棧指針複製給幀指針。
二:指令集體繫結構的選擇
做出這些選擇處於當前技術和硬體可行性考慮,有時時為了對高級語言結構提供精簡而高效的支持。
額外的指令,提升編譯出的代碼的空間和時間的有效性。
額外的定址模式:如間接定址 ld @(ra),一般走最少化路線。
歷史上體繫結構類型:
面向棧的體繫結構,所有操作數在棧上
面向記憶體的體繫結構,大部分指令都是操作記憶體中的操作數
面向寄存器的體繫結構,本章所討論的,大部分指令都是操作寄存器中的操作數
混合類型,針對特定應用可以選擇其中某一種,如IBM PowerPC和Intel x86
指令格式:
按指令結構分:
零操作數 HALT,NOP
單操作數 INC/DEC NOT
雙操作數 ADD, STORE,LOAD,MOV
三操作數ADD,LOAD
廣義分:
所有指令等長
優勢:簡化實現,可以馬上對欄位進行解釋
劣勢:可能浪費空間,需要連接邏輯,設計受限
指令長度可變
優勢:沒有空間浪費,設計不受限,對編譯器更有針對性
劣勢:複雜,解釋困難
影響處理器設計的問題
一個指令集的成敗很大以來與市場的接納程度。
應用程式對指令集設計的影響巨大,視頻,音頻,游戲,圖像等。
其他:操作系統,對現代語言的支持,存儲系統,並行性,調試,虛擬化,容錯性,安全性等等。