6

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

  1. 它们会使 CPU 过于复杂
  2. 他们将毫无用处
  3. 也许以上两者

我想出了第二个,因为我从未见过编译程序(比如 gcc)使用albh其中任何一个。

4

5 回答 5

9

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

至于他们为什么不做,其实很简单:如果他们真的想做,就需要彻底重新设计指令编码。在最初的设计中,他们用尽了所有适合他们用来指定寄存器的字段大小的代码。事实上,他们已经使用了一些技巧,其中编码的含义取决于模式,并且如果您需要使用不同的大小,还有地址大小和操作数大小前缀。例如,要在 32 位模式下运行时使用 AX,指令将在指令本身之前有一个操作数覆盖前缀。如果他们真的非常想要,他们可以扩展这个概念以指定诸如“寄存器 X 的第 16-23 位中的字节”之类的东西,但这会使解码更加复杂,

于 2011-03-15T23:24:46.350 回答
6

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

为什么没有包含 EAX 高字节的寄存器?

于 2011-03-15T21:06:23.487 回答
5

除了Jerry正确提到的指令编码问题之外,这里还有其他事情在起作用。

大多数重要的 CPU 都是流水线的:这意味着在普通操作中,指令在先前的指令完成执行之前开始执行。这意味着处理器必须检测指令对早期指令的任何依赖关系,并阻止指令执行,直到它所依赖的数据(或条件标志)可用[1]。

为寄存器的不同部分命名会使这种依赖性跟踪变得复杂。如果我写:

mov  ax,  dx
add  eax, ecx

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

为寄存器的高半部分命名会增加一组额外的必须跟踪的部分寄存器名称,这会增加芯片面积和功耗,但几乎没有什么好处。归根结底,这就是 CPU 设计决策的制定方式:芯片面积(和功率)与收益之间的权衡。

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

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

于 2011-03-15T23:45:28.693 回答
2

补充一下 Jerry 和 Stephen 到目前为止所说的内容。

首先的想法是你必须尽量保守你的操作码/指令编码。从斧头开始,啊,还有人。在使用 eax 提供对高位寄存器的基于字节的访问时是否增加了值(除了已经存在的旋转或移位)?并不真地。如果您正在执行字节操作,为什么要使用 32 位寄存器以及为什么使用高字节?也许利用可用的东西或容忍可用的东西并利用其他领域的优势,以不同的方式优化代码。

我认为世界上大多数指令集没有这四个名称来表示同一个寄存器是有原因的。而且我不认为是专利在起作用。在当时,它可能是一个很酷的功能或设计。可能起源于将人们从 8 位处理器过渡到这个 8/16 位的东西。无论如何,我认为 al, ah, ax, eax 是糟糕的设计,每个人都从中吸取了教训。正如斯蒂芬提到的那样,您遇到了硬件问题,如果您严格按照直接逻辑来实现它,那将是一团糟,一个多路复用器的老鼠巢将所有东西连接起来(对速度不利,对功率不利),然后您就会进入时机斯蒂芬正在做的噩梦。但是这个指令集有微编码的历史,所以你基本上是在用其他处理器模拟这些指令,并且以同样的方式增加了那个噩梦。明智的做法是将 ax 重新定义为 32 位并摆脱 ah 和 al。从设计的角度来看是明智的,但在可移植性方面是不明智的(对工程有利,对营销、销售等不利)。我认为,旧指令集不限于历史书籍和博物馆的原因是(以及其他一些原因)是因为反向兼容性。

我强烈建议学习一些其他的指令集,包括新的和旧的。msp430、ARM、thumb、mips、6502、z80、PIC(不是 mips 的旧版本)等。仅举几例。看到指令集之间的差异和相似之处是非常有教育意义的 IMO。并且取决于您对理解的深入程度(可变字长与固定长度等),了解我们在进行 16 位到 32 位以及最近的 32 位到 64 位转换时可以为英特尔提供哪些选择,同时努力保持市场份额.

我认为他们当时选择的解决方案是正确的选择,在通常解码为 16 位操作码的前面插入一个以前未定义的操作码,将其转换为 32 位操作码。或者有时如果没有紧随其后的值(需要知道要阅读多少),则有时不会。它似乎符合当时的指令集。所以又回到了 Jerry 的回答,原因是结合了 8/16 位指令集的设计历史和扩展它的原因。当然,他们可以很容易地使用类似的编码以 ax,ah,al 方式提供对高 16 位的访问,并且他们可以很容易地将四个基址寄存器 A、B、C、D 乘以 8 或 16或 32 个通用寄存器(A、B、C、D、E、F、G、H、...),同时保持反向兼容。

于 2011-03-16T02:35:44.253 回答
1

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

最初有四个,16bit 的 AX/BX/BP/SP 和 8bit 的 AL/AH/BL/BH。

再添加两个得到 CX/DX 加上 CL/CH/DL/DH。没有更多的 8 位寄存器,但在 16 位的寄存器选择中仍有两个未使用的值。

这是由索引 regs DI/SI 在英特尔架构的另一个版本中提供的。

完成后,他们已经用尽了 3 个寄存器选择位(并且无法为 SI/DI/BP/SP 提供 8 位寄存器)。

因此,AMD64 64 位模式设法使寄存器集翻倍的方式是使用前缀字节(“使用新的 regs”-前缀),类似于传统 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 寄存器了但向量寄存器代替。如果您需要较大值的上/下半部分(或其他部分)之间的对称性,请使用向量寄存器。

于 2011-03-16T15:12:25.347 回答