为什么英特尔没有提供其CPU寄存器的高阶部分?

时间:2011-03-15 20:54:27

标签: assembly x86 cpu-registers

在汇编中进行编程并进行某种字符串操作时,我使用alah,有时还有其他人来保存字符,因为这样可以让我在寄存器中保留更多数据。我认为这是一个非常方便的功能,但英特尔的工程师似乎不同意我,因为他们没有使寄存器的两个高位字节可访问(或者我错了?)。我不明白为什么。我想了一会儿,我的猜测是:

  1. 他们会让CPU太复杂
  2. 他们没用了
  3. 或许都是上述
  4. 我想到了第二,因为我从未见过使用albh或其中任何一个的已编译程序(比如使用gcc)。

5 个答案:

答案 0 :(得分:9)

虽然它有点笨拙,但你可以用rol reg,16(或ror reg,16交换寄存器的一半,如果你愿意的话)。在Netbust CPU(Pentium IV)上效率非常低,但在大多数较新(或较旧)的CPU上,你通常会有一个桶形移位器在一个时钟内完成。

至于为什么他们没有这样做,这很简单:他们需要彻底重新设计指令编码,如果他们真的想这样做的话。在原始设计中,他们使用了所有符合其用于指定寄存器的字段大小的代码。实际上,他们已经使用了一些hack,其中编码的含义取决于模式,如果需要使用不同的大小,则有地址大小和操作数大小前缀。例如,要在32位模式下运行时使用AX,该指令将在指令本身之前具有操作数覆盖前缀。如果他们真的想要足够严重,他们可以扩展这个概念来指定诸如“寄存器X的16-23位中的字节”之类的东西,但它会使解码变得更复杂,并且解码x86指令已经相对痛苦了

答案 1 :(得分:6)

简短回答是因为它是如何从16位演变而来的。

Why is there not a register that contains the higher bytes of EAX?

答案 2 :(得分:5)

除了 Jerry 正确提及的指令编码问题之外,还有其他一些工作在这里。

大多数非平凡的CPU是流水线的:这意味着在普通操作中,指令在前一条指令完成执行之前开始执行。这意味着处理器必须检测先前指令上的指令的任何依赖性,并防止指令执行,直到它所依赖的数据(或条件标志)可用[1]。

为寄存器的不同部分设置名称会使此依赖关系跟踪变得复杂。如果我写:

mov  ax,  dx
add  eax, ecx

然后核心需要知道axeax的一部分,并且添加应该等到移动结果可用。这称为部分寄存器更新;虽然看起来非常简单,但硬件设计人员通常不喜欢它们,并尽量避免需要尽可能地跟踪它们(特别是在现代无序处理器中)。

具有寄存器高半部分的名称会增加一组必须跟踪的部分寄存器名称,这会增加芯片面积和功耗,但收益甚微。在一天结束时,这就是CPU设计决策的方式:模具面积(和功率)与效益的权衡。

部分寄存器更新并不是唯一一个因寄存器的高位部分具有名称而变得复杂的事情,但它是最简单的解释之一;还有许多其他小东西需要在现代x86 CPU中变得更加复杂以支持它;综合考虑,额外的复杂性将是巨大的。

[1]还有其他方法可以解决依赖关系,但为简单起见,我们在此忽略它们;他们引入了类似的问题。

答案 3 :(得分:2)

补充杰瑞和斯蒂芬到目前为止所说的话。

首先想到的是你必须尝试保守你的操作码/指令编码。进入它开始用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,......),同时保持反向兼容。

答案 4 :(得分:1)

实际上,传统的x86操作码允许选择操作数大小(有时作为特定指令编码,有时通过前缀字节)和寄存器号选择位。对于寄存器选择,指令编码中始终有三位。这允许总共八个寄存器。

最初有4位,AX / BX / BP / SP为16位,AL / AH / BL / BH为8位。

再加两个给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位字段可以通过例如单独访问。随机指令,实际上Intel / AMD在其优化库中提供的大量字符串操作代码现在不再使用普通的CPU寄存器,而是使用向量寄存器。如果在某个较大值的上/下半部分(或其他部分)之间需要对称,请使用向量寄存器。