[英]Why can't 16-bit instructions access the high registers of the general purpose registers
[英]Why didn't Intel made the high order part of their CPUs' registers available?
當在匯編中進行編程並進行某種字符串操作時,我使用al
, ah
和其他人來保存字符,因為這允許我在寄存器中保留更多數據。 我認為這是一個非常方便的功能,但英特爾的工程師似乎不同意我,因為他們沒有使寄存器的兩個高位字節可訪問(或者我錯了?)。 我不明白為什么。 我想了一會兒,我的猜測是:
我想到了第二,因為我從未見過編譯程序(比如使用gcc)使用al
或bh
或其中任何一個。
雖然它有點笨拙,但你只需將寄存器的一半換成rol reg,16
(或ror reg,16
,如果你願意的話)。 在Netbust CPU(Pentium IV)上效率非常低,但在大多數較新(或較舊)的CPU上,你通常會有一個桶形移位器在一個時鍾內完成。
至於為什么他們沒有這樣做,這很簡單:他們需要徹底重新設計指令編碼,如果他們真的想這樣做的話。 在原始設計中,他們使用了所有符合其用於指定寄存器的字段大小的代碼。 實際上,他們已經使用了一些hack,其中編碼的含義取決於模式,如果需要使用不同的大小,則有地址大小和操作數大小前綴。 例如,要在32位模式下運行時使用AX,該指令將在指令本身之前具有操作數覆蓋前綴。 如果他們真的想要足夠嚴重,他們可以擴展這個概念來指定諸如“寄存器X的16-23位中的字節”之類的東西,但它會使解碼變得更復雜,並且解碼x86指令已經相對痛苦了。
簡短的回答是因為它是如何從16位進化而來的。
除了Jerry正確提到的指令編碼問題之外,還有其他一些工作要做。
大多數非平凡的CPU都是流水線的:這意味着在普通操作中,指令在前一條指令完成執行之前開始執行。 這意味着處理器必須檢測先前指令上的指令的任何依賴性,並防止指令執行,直到它所依賴的數據(或條件標志)可用[1]。
為寄存器的不同部分設置名稱會使這種依賴性跟蹤變得復雜。 如果我寫:
mov ax, dx
add eax, ecx
然后核心需要知道ax
是eax
一部分,並且add應該等到移動的結果可用。 這稱為部分寄存器更新 ; 雖然看起來非常簡單,但硬件設計人員通常不喜歡它們,並盡量避免需要盡可能地跟蹤它們(特別是在現代無序處理器中)。
具有寄存器高半部分的名稱會增加一組必須跟蹤的部分寄存器名稱,這會增加芯片面積和功耗,但幾乎沒有什么好處。 在一天結束時,這就是CPU設計決策的方式:模具面積(和功率)與效益的權衡。
通過為寄存器的高位部分命名,部分寄存器更新不是唯一復雜的事情,但它是最簡單的解釋之一; 還有許多其他小東西需要在現代x86 CPU中變得更加復雜以支持它; 綜合考慮,額外的復雜性將是巨大的。
[1]還有其他方法可以解決依賴關系,但為簡單起見,我們在此忽略它們; 他們引入類似的問題。
加上Jerry和Stephen到目前為止所說的話。
首先想到的是你必須嘗試保守你的操作碼/指令編碼。 進入它開始用ax,啊和al。 當轉向eax以提供對上層寄存器的基於字節的訪問時(除了已經提供的旋轉或移位之外),是否有一個值? 並不是的。 如果您正在進行字節操作,為什么使用32位寄存器以及為什么使用高位字節? 也許以不同的方式優化代碼,利用可用的東西或容忍可用的東西,並在其他領域中占據優勢。
我認為有一個原因是世界上大多數的指令集都沒有這四個名字用於相同的寄存器。 我不認為這是正在發揮作用的專利。 在它的一天,它可能是一個很酷的功能或設計。 可能源於將8位處理器的人員轉換為這種8/16位的東西。 無論如何,我認為al,啊,ax,eax是糟糕的設計,每個人都從中學到了東西。 斯蒂芬提到你有硬件問題在起作用,如果你嚴格按照直接邏輯實現這一點,它就是一堆亂七八糟的混合物,用於連接所有東西(速度不好,功率不好),然后你進入時間噩夢斯蒂芬正在接受。 但是這個指令集有一個微編碼的歷史,所以你基本上用其他處理器模擬這些指令,並以同樣的方式增加了這個噩夢。 明智的做法是重新定義ax為32位並擺脫啊和al。 從設計角度看是明智的,但對於便攜性來說是不明智的(對工程有利,對營銷,銷售等不利)。 我認為,由於反向兼容性,疲憊的舊指令集不限於歷史書籍和博物館的原因(其中一些原因)。
我強烈建議學習一些新的和舊的指令集。 msp430,ARM,拇指,mips,6502,z80,PIC(舊的不是mips)等等。僅舉幾例。 看到指令集之間的差異和相似性是非常有教育意義的IMO。 並且取決於你進入理解的深度(可變字長與固定長度等),了解在進行16到32位以及最近的32位到64位轉換時我們可用於英特爾的哪些選擇,同時試圖保留市場份額。
我認為他們當時選擇的解決方案是正確的選擇,在通常解碼為16位操作碼的前面插入一個以前未定義的操作碼,將其轉換為32位操作碼。 或者有時不會,如果沒有緊隨其后的值(需要知道要閱讀多少)。 它似乎符合當時的指令集。 所以它回到了Jerry的答案,原因是8/16位指令的設計組合了歷史和擴展它的原因。 當然,他們可以像使用類似的編碼一樣輕松地提供對ax中的高16位的訪問,啊,方式,他們可以輕松地將四個基址寄存器A,B,C,D乘以8或16或32個通用寄存器(A,B,C,D,E,F,G,H,......),同時保持反向兼容。
實際上,傳統的x86操作碼允許選擇操作數大小(有時作為特定指令編碼,有時通過前綴字節)和寄存器號選擇位。 對於寄存器選擇,指令編碼中始終有三位。 這允許總共八個寄存器。
最初有四個,16位的AX / BX / BP / SP和8位的AL / AH / BL / BH。
再加兩個給CX / DX加CL / CH / DL / DH。 不再有8位寄存器,但16位寄存器選擇中仍有兩個未使用的值。
這是由索引注冊DI / SI在英特爾架構的另一個版本中提供的。
完成后,他們已經耗盡了3個寄存器選擇位(並且無法為SI / DI / BP / SP提供8位寄存器)。
因此,AMD64 64位模式設法使寄存器集加倍的方式是使用前綴字節(“使用新的regs”-prefix),類似於傳統的x86代碼在16和32位操作之間選擇的方式。 使用相同的方法提供8位寄存器,其中沒有“傳統”,即用於SP/BP/SI/DI
。
為了說明,請參閱以下指令編碼:
0: 00 c0 add %al,%al
2: 00 c1 add %al,%cl
4: 00 c2 add %al,%dl
6: 00 c3 add %al,%bl
8: 00 c4 add %al,%ah
a: 00 c5 add %al,%ch
c: 00 c6 add %al,%dh
e: 00 c7 add %al,%bh
10: 40 00 c4 add %al,%spl
13: 40 00 c5 add %al,%bpl
16: 40 00 c6 add %al,%sil
19: 40 00 c7 add %al,%dil
並且,對於[16bit / 64bit] / 32bit,並排,因為它是如此說明:
0 : [66/48] 01 c0 add %?ax,%?ax
2/3 : [66/48] 01 c1 add %?ax,%?cx
4/6 : [66/48] 01 c2 add %?ax,%?dx
6/9 : [66/48] 01 c3 add %?ax,%?bx
8/c : [66/48] 01 c4 add %?ax,%?sp
a/f : [66/48] 01 c5 add %?ax,%?bp
c/12: [66/48] 01 c6 add %?ax,%?si
e/15: [66/48] 01 c7 add %?ax,%?di
前綴0x66
標記為16位操作, 0x48
是64位操作的前綴字節之一(如果您的目標和/或源是“新”高編號寄存器之一,則它將是不同的)。
要回到原來的問題,如何訪問高位; 好吧,較新的CPU有SSE指令用於此目的; 向量寄存器的每個8/16/32/64位字段可以通過例如shuffle指令單獨訪問,事實上,Intel / AMD在其優化庫中提供的大量字符串操作代碼現在不再使用普通的CPU寄存器了但是矢量注冊了。 如果需要在較大值的上半部分或下半部分(或其他分數)之間進行對稱 ,請使用向量寄存器。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.