?? chapter2.htm
字號:
<BR><BR>如果堆棧的底部在編譯時刻不能被決定,你就不能通過sp來存取堆棧變量,因此fp被
<BR>初始化為一個相對與該函數(shù)堆棧的一個常量的位置。這種用法對其他函數(shù)是不可見 <BR>的。 <BR><BR>* ra:
當調(diào)用任何一個子函數(shù)時,返回地址存放在ra寄存器中,因此通常一個子程 <BR>序的最后一個指令是jr ra.
<BR><BR>子函數(shù)如果還要調(diào)用其他的子函數(shù),必須保存ra的值,通常通過堆棧。
<BR><BR>對於浮點寄存器的用法,也有一個相應的標準的約定。我們將在7.5節(jié)。在這里,我
<BR>們已經(jīng)介紹了MIPS引入的寄存器的用法約定。最近在約定方面有一些演化,我們將 <BR>在10.8節(jié)中介紹這些變化,比如調(diào)用約定的一些新標準。
<BR><BR>2.3 整數(shù)乘法部件與寄存器 <BR><BR>MIPS 體系結構認為整數(shù)乘法部件非常重要,需要一個單獨的硬件指令。這一點在RISC芯
<BR>片里不多見。一個另外做法是通過標準的整數(shù)運算流水線部件來實現(xiàn)一個乘法。這
<BR>意味著對於每個乘法指令,需要一段軟件過程(來模擬一個乘法指令)。早期的Spacr <BR>CPU就是這樣做的。
<BR><BR>另外一個用來避免設計一個整數(shù)乘法器的做法是通過浮點運算器來實現(xiàn)乘法。Motorola的 <BR>88000
CPU家族就是提供了這樣的解決方案。這樣的缺點是損失了MIPS浮點運算器是 <BR>用來做浮點運算的設計初衷。
<BR><BR>早期的MIPS乘法運算器不是特別快。它的基本功能是將兩個寄存器大小的值做一個 <BR>乘法并將兩個寄存器大小的結果存放在乘法部件里。mfhi,
mflo指令用來將結果的 <BR>兩部分分別放入指定的通用寄存器里。
<BR><BR>與整數(shù)運算結果不一樣的是,乘法結果寄存器是互鎖的(inter-locked)。試圖在乘
<BR>法結束之前對結果寄存器的讀操作將被暫停直到乘法運算結束。 <BR><BR>整數(shù)乘法器也可以執(zhí)行兩個通用寄存器的除法操作。lo寄存器用來存放結果(商),
<BR>hi寄存器用來存放余數(shù)。 <BR><BR>MIPS CPU的整數(shù)乘法部件操作相對而言比較慢:乘法需要5-12個時鐘周期,除法需
<BR>要35-80個時鐘周期(與具體CPU的實現(xiàn)有關,如操作數(shù)的大小)。相對一個同樣的雙
<BR>精度浮點運算操作,乘法和除法操作是太慢了。乘/除法并且在內(nèi)部不是靠流水線來
<BR>實現(xiàn)的??梢娤鄳挠布崿F(xiàn)是犧牲了速度以換取(指令)簡單和節(jié)省芯片大小。
<BR><BR>匯編器提供了一個合成的乘法指令用來執(zhí)行乘法并將結果取出放回一個通用寄存器。
<BR>MIPS公司的匯編器會通過一系列的移位和加法操作來替換(硬件)的乘法指令, 如果
<BR>匯編器優(yōu)化覺得這樣更快的話。我對於這一點的意見是優(yōu)化的工作應該有編譯器來 <BR>完成,而不是有匯編器來做。
<BR><BR>乘法部件不是流水線構造的。每一次只能執(zhí)行一條指令。上一次的結果將丟失如果
<BR>下一條乘法指令又開始了,上一次的結果不會象流水線結構那樣被寫到流水線的write-back階
<BR>段。(譯者注:在流水線方式下,在write-back階段,寄存器-寄存器指令的結果將
<BR>被寫回到結果寄存器)。這一點如果不注意的話,將導致一些非常難理解的問題,導 <BR>致你的程序的結果不對,比如中斷的打擾使得你剛才的乘法結果被沖掉了。
<BR><BR>如果一個mfhi或mflo指令在還沒有走到流水線的write-back階段而被中斷或異常打
<BR>斷,系統(tǒng)將會重新啟動上述讀取操作,廢掉上一次的讀取。但是如果下一條指令是
<BR>乘法指令并且完成了ALU階段,該乘法指令會與異常處理并行的執(zhí)行,并有可能覆蓋
<BR>掉hi和ho寄存器里的內(nèi)容。那么上述mfhi或mflo的重新執(zhí)行將會得到錯誤的結果。
<BR>由於這個原因,乘法指令一般不要緊跟在mfhi/mflo指令后面,要隔開兩條指令(譯 <BR>者著:從而防止CPU的指令預取)
<BR><BR><BR>2.4 加載與存儲:尋址方式 <BR><BR>如前面所言,MIPS只有一種尋址方式。任何加載或存儲機器指令可以寫成 <BR>lw $1,
offset($2) <BR>你可以使用任何寄存器來作為目標和源寄存器。offset偏移量是一個有符號的16位
<BR>的數(shù)字(因此可以是在-32768與32767之間的任何一值)。用來加載的程序地址是源寄
<BR>存器與偏移量的和所構成的地址。這種尋址方式一般已足夠存取一個C語言的結構(偏
<BR>移量是這個結構的起始地址到所要存取的結構成員之間的距離)。這種尋址方式實現(xiàn)
<BR>了一個通過一個常量來索引的數(shù)組;并足夠使得可以存取堆棧上的函數(shù)變量或楨指
<BR>針;可以提供一個比較合適大小的以gp為基址的全局空間以存取靜態(tài)和外部數(shù)據(jù)。
<BR><BR><BR>匯編器提供一個簡單直接存取方式的匯編格式從而可以加載一個在連接時刻才能決 <BR>定地址的變量的值。
<BR><BR>許多更復雜的方式,如雙寄存器或可伸縮的索引,都需要多個指令的組合。 <BR><BR>2.5 存儲器與寄存器的數(shù)據(jù)類型 <BR><BR>MIPS
CPU可以在一個單一操作中存儲1到8個字節(jié)。文檔中和用來組成指令助記符的 <BR>命名約定如下: <BR><BR>C名字 MIPS名字 大小(字節(jié)) 匯編助記符
<BR>longlong dword 8 "d"代表ld <BR>int/long word 4 "w"代表lw <BR>short halfword 2
"h"代表lh <BR>char byte 1 "b"代表lb <BR><BR>2.5.1 整數(shù)數(shù)據(jù)類型
<BR><BR>byte和short的加載有兩種方式。帶符號擴展的lb和lh指令將數(shù)據(jù)值存放在32位寄存
<BR>器的低位中并剩下的高位用符號位的值來擴充(位7如果是一個byte,位15如果是一
<BR>個short)。這樣就正確地將一個帶符號整數(shù)放入一個32位的帶符號的寄存器中。
<BR><BR>不帶符號指令lbu和lhu用0來擴充數(shù)據(jù),將數(shù)據(jù)存放縱32位寄存器的低位中,并將高 <BR>位用零來填充。
<BR><BR>例如,如果一個byte字節(jié)寬度的存儲器地址為t1,其值為0xFE(-2或254如果是非符
<BR>號數(shù)),那么將會在t2中放入0xFFFFFFFE(-2作為一個符號數(shù))。t3的值會是0x000000FE(254作 <BR>為一個非符號數(shù))
<BR><BR>lb t2, 0(t1) <BR>lbu t3, 0(t1) <BR><BR>上述描述是假設一個32位的MIPS CPU。但是MIPS
III或其上的體系結構實現(xiàn)了64位 <BR>寄存器。可見所有的部分word字的加載(包括非符號數(shù))都帶符號(包括0)擴充到高32位。
<BR>這看上去很奇怪但卻是很有用的。這將在2.7.3節(jié)中解釋這一點。
<BR><BR>這些較小長度的整數(shù)擴充到較長的整數(shù)的細微區(qū)別是由於C語言可移植性的歷史原因
<BR>造成的?,F(xiàn)代C語言標準定義了非常明確的規(guī)則來避免可能的二義性。在不能直接作
<BR>8位和16位精度的算術的機器中,如MIPS,編譯器對任何包含short和char變量的表
<BR>達式中需要插入額外的指令以確保數(shù)據(jù)該溢出時得溢出:這一點是不希望的,程序 <BR>效率非常差。當移植一個使用小整數(shù)變量的代碼到MIPS
CPU上的時候,你應該考慮 <BR>找出那些可以安全的轉換成整數(shù)的變量。 <BR><BR>2.5.2 沒對齊的加載和存儲
<BR><BR>MIPS體系結構中,正常的加載和存儲必須對齊。半字(halfwords)必須從2個字節(jié)的
<BR>邊界加載;字(word)必須從4個字節(jié)的邊界。一個加載沒有對齊的地址的加載指令會
<BR>導致CPU進入異常處理。因為CISC體系結構,例如MC680x0和Intel的x86確實能夠處
<BR>理非對齊的加載和存儲,當移植軟件到MIPS體系結構時,你可能會遇到這個問題。
<BR>一個極端情況是你或許想安裝一個異常處理程序來負責相應的加載操作從而使得地
<BR>址對齊的操作對用戶程序是透明的。但是這種做法使得程序效率非常慢,除非這樣 <BR>的異常處理非常少。
<BR><BR>所有C語言的數(shù)據(jù)類型將嚴格的按照其數(shù)據(jù)類型的大小對齊。
<BR><BR>當你不知道你要操作的數(shù)據(jù)是對齊的或者說就是不對齊的,MIPS體系結構允許通過
<BR>兩條指令來完成這個非對齊的存取(比通過一些列的字節(jié)的存取然后移位,加法的效
<BR>率高得多)。這些代理指令的操作很隱含,比較難以掌握,通常是有宏指令ulw的產(chǎn) <BR>生的。詳細可見8.4.1節(jié)。
<BR><BR>MIPS另外還提供宏指令ulh(非對齊的加載半字)。這也是通過合成指令來完成的--兩 <BR>個加載操作,一個移位和一個位或操作。
<BR><BR>通常,C編譯器負責將所有的數(shù)據(jù)進行正確的對齊。但是在有些情況下(但從一個文
<BR>件中讀取數(shù)據(jù)或與一個不同的CPU共享數(shù)據(jù))能夠處理非對齊的整數(shù)數(shù)據(jù)是必須的,
<BR>一些編譯器允許你設定一個數(shù)據(jù)類型是非對齊的,編譯器將會產(chǎn)生相應的特殊代碼 <BR>來處理。ANSI提供#progma align
nn,GNU是通過更簡潔packed結構屬性類型來指定。
<BR><BR><BR>即使你的編譯器實現(xiàn)了packd數(shù)據(jù)類型,編譯器并不保證會使用特殊的MIPS指令來實 <BR>現(xiàn)非對齊的存取。 <BR><BR>2.5.3
內(nèi)存中的浮點數(shù)據(jù) <BR><BR>從內(nèi)存中將數(shù)據(jù)加載到浮點寄存器中不會經(jīng)過任何檢查--你可以加載一個非法的浮
<BR>點數(shù)據(jù)(實際上,你可以加載任意的數(shù)據(jù)模式),并不會得到浮點運算錯誤直到對這 <BR>些數(shù)據(jù)進行操作。
<BR><BR>在32位處理器上,這允許你通過一個加載將一個單精度的數(shù)據(jù)放入一個偶數(shù)號的浮
<BR>點寄存器中,你也可以通過一個宏指令加載一個雙精度的數(shù)據(jù),因此在一個32位的 <BR>CPU上,匯編指令 <BR><BR>l.d $f2, 24(t1)
<BR><BR>被擴充為兩個連續(xù)的寄存器加載: <BR><BR>lwc1 $f2, 24(t1) <BR>lwc1 $f3, 28(t1)
<BR><BR>在一個64位CPU上, l.d是機器指令ldc1的別名。ldc1完成64位數(shù)據(jù)的加載工作。
<BR><BR>任何一個遵循MIPS/SGI規(guī)則的C編譯器都將8byte的long(長整數(shù)),雙精度浮點變量 <BR>在8byte
的地址邊界上對齊。32位硬件不需要這個要求,對齊是為了向上的兼容性:
<BR>64位CPU如果加載一個沒有在8byte上對奇的double變量,CPU將進入錯誤處理,進入 <BR>異常。 <BR><BR>2.6 匯編語言的合成指令
<BR><BR>雖然從體系結構的原因我們不能直接用一條指令來完成將一個32位的常量取入一個 <BR>寄存器中,
但是寫MIPS機器碼或許太沉悶了。匯編語言程序員不想每次都得考慮這 <BR>些。因此MIPS公司的匯編器(和其他的MIPS匯編器)將會為你合成一些指令。你只需
<BR>要寫一個加載立即數(shù)指令,匯編器會知道什么時候通過兩條機器指令來實現(xiàn)之。
<BR><BR>顯然這是很有用的,但是同時自從發(fā)明之后也就一直被亂用。許多MIPS匯編器通過
<BR>將體系結構的特點掩蓋起來從而使得不需要合成指令。在本書中,我們將試圖盡量
<BR>少用合成指令,當使用時,會給讀者指出來。另外,在下面的指令列表中,我們將 <BR>會指出合成指令與機器指令的區(qū)別。
<BR><BR>我的感覺是合成指令是用來幫助程序員的,嚴肅的編譯器應該嚴格的一對一的產(chǎn)生
<BR>機器指令代碼。但是在這個不盡善盡美的世界里,還是有許多編譯器產(chǎn)生合成指令。 <BR><BR><BR>匯編器提供的有用的方面包括下列: <BR><BR>*
一個32位的立即數(shù)加載:你可以在數(shù)據(jù)碼中加載任何數(shù)據(jù)(包括一個在連接階段決 <BR>定的內(nèi)存地址),匯編器將會把其拆開成為兩個指令,加載這個數(shù)據(jù)的前半部分和后
<BR>半部分。 <BR><BR>*從一個內(nèi)存地址加載:你可以從一個內(nèi)存變量來作一個加載。匯編器通常會將這個
<BR>變量的高位地址放入一個暫時的寄存器中,然后將這個變量的低位作為一個加載的
<BR>偏移量。當然這不包括C函數(shù)里的局部變量。局部變量通常定義在堆棧上或寄存器中。
<BR><BR><BR>*對內(nèi)存變量的快速存?。阂恍〤程序包含了許多對static和extern變量的存取, 對
<BR>它們加載與存儲用load/store兩條指令開銷太大了。一些編譯系統(tǒng)避開了這一點,
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -