%AX = (%AH + %AL)
那么为什么不%EAX = (%SOME_REGISTER + %AX)
注册一些%SOME_REGISTER
呢?
%AX = (%AH + %AL)
那么为什么不%EAX = (%SOME_REGISTER + %AX)
注册一些%SOME_REGISTER
呢?
只是为了澄清一下。在 1970 年代早期的微处理器时代,CPU 只有少量的寄存器和非常有限的指令集。通常,算术单元只能在单个 CPU 寄存器上运行,通常称为“累加器”。8 位 8080 和 Z80 处理器上的累加器称为“A”。还有 6 个其他通用 8 位寄存器:B、C、D、E、H 和 L。这六个寄存器可以配对形成 3 个 16 位寄存器:BC、DE 和 HL。在内部,累加器与标志寄存器组合形成 AF 16 位寄存器。
当英特尔开发 16 位 8086 系列时,他们希望能够移植 8080 代码,因此他们保持相同的基本寄存器结构:
8080/Z80 8086
A AX
BC BX
DE CX
HL DX
IX SI
IY DI
由于需要移植 8 位代码,他们需要能够引用 AX、BX、CX 和 DX 的各个 8 位部分。这些被称为 AL、AH 用于 AX 的低字节和高字节,以此类推用于 BL/BH、CL/CH 和 DL/DH。Z80 上的 IX 和 IY 仅用作 16 位指针寄存器,因此无需访问 SI 和 DI 的两半。
当 80386 在 1980 年代中期发布时,他们创建了所有寄存器的“扩展”版本。所以,AX 变成了 EAX,BX 变成了 EBX 等等。不需要访问这些新扩展寄存器的前 16 位,所以他们没有创建 EAXH 伪寄存器。
AMD 在生产第一批 64 位处理器时采用了相同的技巧。AX 寄存器的 64 位版本称为 RAX。所以,现在你有一些看起来像这样的东西:
|63..32|31..16|15-8|7-0|
|AH.|AL.|
|AX.....|
|EAX............|
|RAX...................|
这里发布了很多答案,但没有一个真正回答给定的问题:为什么没有直接编码 EAX 的高 16 位或 RAX 的高 32 位的寄存器?答案归结为 x86 指令编码本身的局限性。
16 位历史课
当英特尔设计 8086 时,他们对许多指令使用了可变长度编码方案。这意味着某些极其常见的指令POP AX
,MOV CX, [BX+SI+1023]
如, 8B 88 FF 03)。
这似乎是一个合理的解决方案,但是当他们设计它时,他们填满了大部分可用空间。因此,例如,POP
八个单独的寄存器(AX、CX、DX、BX、SP、BP、SI、DI)有 8 条指令,它们填写了操作码 58 到 5F,而操作码 60 完全是另外一回事(PUSHA
) ,就像操作码 57 ( PUSH DI
) 一样。在这些之后或之前没有任何空间。即使推送和弹出段寄存器——这在概念上几乎与推送和弹出通用寄存器相同——也必须在不同的位置进行编码(大约在 06/0E/16/1E 左右),因为旁边没有空间其余的推送/弹出指令。
同样,用于复杂指令的“mod r/m”字节MOV CX, [BX+SI+1023]
只有三位用于对寄存器进行编码,这意味着它总共只能表示八个寄存器。如果您只有八个寄存器,那很好,但如果您想要更多,就会出现真正的问题。
(这里有一张 x86 架构中所有这些字节分配的出色映射:http: //i.imgur.com/xfeWv.png。请注意主映射中没有剩余空间,一些指令重叠字节,甚至如何由于 MMX 和 SSE 指令,现在使用了大部分辅助“0F”映射。)
迈向 32 位和 64 位
所以为了让 CPU 设计从 16 位扩展到 32 位,他们已经遇到了设计问题,他们用前缀字节解决了这个问题:通过在所有标准 16 位前面添加一个特殊的“66”字节指令,CPU 知道您需要相同的指令,但需要 32 位版本 (EAX) 而不是 16 位版本 (AX)。设计的其余部分保持不变:整个 CPU 架构中仍然只有八个通用寄存器。
必须进行类似的黑客操作才能将架构扩展到 64 位(RAX 和朋友);在那里,通过添加另一组前缀代码 ( REX
, 40-4F) 来解决问题,这意味着“64 位”(并有效地在“mod r/m”字段中添加了另外两位),并且还丢弃了奇怪的旧没有人使用和重用他们的字节码来更新的指令。
8 位寄存器旁白
那么,要问的一个更大的问题是,如果设计中只有八个寄存器的空间,那么像 AH 和 AL 这样的东西是如何工作的。答案的第一部分是没有“ PUSH AL
”之类的东西——一些指令根本无法对字节大小的寄存器进行操作!唯一能做到的是一些特殊的怪事(比如AAD
和XLAT
) 和特殊版本的“mod r/m”指令:通过在“mod r/m”字节中翻转一个非常特定的位,这些“扩展指令”可以翻转以在 8 位寄存器上操作,而不是在16 位的。恰好也有八个 8 位寄存器:AL、CL、DL、BL、AH、CH、DH 和 BH(按此顺序),并且与可用的八个寄存器插槽非常吻合在“mod r/m”字节中。
英特尔当时指出,8086 设计应该与 8080/8085 “源代码兼容”:8086 中的每个 8080/8085 指令都有一条等效指令,但它没有使用相同的字节码(它们甚至不接近),并且您必须重新编译(重新组装)您的程序以使其使用新的字节码。但是“源兼容”是旧软件的一种前进方式,它允许 8085 的单独 A、B、C 等以及组合“BC”和“DE”寄存器仍然可以在新处理器上工作,即使它们现在是称为“AL”和“BL”和“BX”和“DX”(或任何映射)。
所以这才是真正的答案:并不是英特尔或 AMD 故意“遗漏”了 EAX 的高 16 位寄存器,或 RAX 的高 32 位寄存器:而是高 8 位寄存器是一个奇怪的遗留历史异常,并且考虑到架构向后兼容的要求,以更高的比特大小复制他们的设计将非常困难。
性能考虑
关于为什么那些“高寄存器”还没有被添加,还有另一个考虑因素:在现代处理器架构中,出于性能原因,可变大小的寄存器实际上并没有重叠:AH 和 AL 不是t AX 的一部分,AX 不是 EAX 的一部分,EAX 不是 RAX 的一部分:它们都是底层的独立寄存器,当您操作其中一个时,处理器会在其他寄存器上设置一个无效标志以便它知道当您从其他人那里读取数据时它需要复制数据。
(例如:如果您设置 AL = 5,处理器不会更新 AX。但如果您随后从 AX 读取,处理器会快速将 AL 中的 5 复制到 AX 的低位。)
通过保持寄存器分开,CPU 可以做各种聪明的事情,比如不可见的寄存器重命名,以使您的代码运行得更快,但这意味着如果您使用将小寄存器视为较大寄存器的旧模式,您的代码运行速度会变慢寄存器,因为处理器将不得不停止并更新它们。为了防止所有这些内部簿记失控,CPU 设计人员明智地选择在较新的处理器上添加单独的寄存器,而不是添加更多重叠的寄存器。
(是的,这意味着在现代处理器上,明确地“ MOVZX EAX, value
”确实比旧的、更草率的“ MOV AX, value / use EAX
”方式更快。)
结论
综上所述,如果英特尔和 AMD 真的想要添加更多“重叠”寄存器,他们是否可以?当然。如果有足够的需求,有办法让他们进来。但是考虑到重要的历史包袱、当前的架构限制、显着的性能限制,以及如今大多数代码是由针对非重叠寄存器优化的编译器生成的事实,他们不太可能很快添加这样的东西。
在旧的 8 位时代,有 A 寄存器。
在 16 位时代,有 16 位 AX 寄存器,它被分成两个 8 位部分,AH 和 AL,在那些你仍然想使用 8 位值的时候。
在 32 位时代,引入了 32 位 EAX 寄存器,但 AX、AH 和 AL 寄存器都保留了下来。设计者认为没有必要引入一个新的 16 位寄存器来寻址 EAX 的第 16 位到第 31 位。