为什么32位寄存器上的x86-64指令将整个64位寄存器的高位归零?


118

x86-64英特尔手册之旅中,我阅读了

也许最令人惊讶的事实是诸如MOV EAX, EBX自动将RAX寄存器的高32位清零的指令。

同一来源引用的英特尔文档(手动基本体系结构中的3.4.1.1通用寄存器在64位模式下)告诉我们:

  • 64位操作数在目标通用寄存器中生成64位结果。
  • 32位操作数生成32位结果,并将其零扩展到目标通用寄存器中的64位结果。
  • 8位和16位操作数生成8位或16位结果。目的通用寄存器的高56位或高48位(分别)不会被该操作修改。如果8位或16位运算的结果打算用于64位地址计算,则将寄存器显式符号扩展为完整的64位。

在x86-32和x86-64汇编中,16位指令例如

mov ax, bx

不要显示eax的高位字为零的这种“奇怪”行为。

因此:引入此行为的原因是什么?乍一看似乎是不合逻辑的(但是原因可能是我已经习惯了x86-32程序的怪癖)。


16
如果您通过Google注册“部分注册摊位”,则可以找到有关(几乎可以肯定)他们试图避免的问题的大量信息。
杰里·科芬


4
不只是“最”。AFAIK,所有带有r32目标操作数的指令将高32归零,而不是合并。例如,某些汇编程序将替换pmovmskb r64, xmmpmovmskb r32, xmm,保存REX,因为64位目标版本的行为相同。即使手册的“ 操作”部分分别列出了32个/ 64位dest和64/128 / 256b源的所有6种组合,r32形式的隐式零扩展也复制了r64形式的显式零扩展。我对HW的实施方式感到好奇...
Peter Cordes

2
@HansPassant,循环引用开始。
kchoi

Answers:


97

我不是AMD也不代表他们说话,但是我会以同样的方式做到这一点。因为将高半部分置零不会对先前的值产生依赖性,所以CPU必须等待。该寄存器重命名,如果它没有这样做这样的机制将基本上被击败。

这样,您可以在64位模式下使用32位值编写快速代码,而不必始终显式破坏依赖关系。如果没有这种行为,即使64位模式下的每条32位指令几乎都不会使用,也将不得不等待之前发生的事情。(使用int64位将浪费缓存占用空间和内存带宽;x86-64最有效地支持32和64位操作数大小

8位和16位操作数大小的行为很奇怪。依赖疯狂是现在避免使用16位指令的原因之一。x86-64从8086的8位和386的16位继承了这一点,并决定让8位和16位寄存器在64位模式下的工作方式与在32位模式下的工作方式相同。


另请参见为什么GCC不使用部分寄存器?有关实际CPU如何处理8位和16位部分寄存器(以及随后的全寄存器读取)的实用细节。


8
我认为这并不奇怪,我想他们不想破坏得太多并且保留旧的行为。
Alexey Frunze 2012年

5
@Alex在引入32位模式时,大部分没有旧行为。之前没有重要的部分。.当然,此后无法更改。
哈罗德

1
我说的是16位操作数,为什么在这种情况下高位不为零。它们不在非64位模式下。而且它也保持在64位模式下。
Alexey Frunze

3
我将您的“ 16位指令的行为很奇怪”解释为“在64位模式下16位操作数不会发生零扩展很奇怪”。因此,我对在64位模式下保持相同方式以获得更好的兼容性提出了意见。
Alexey Frunze 2012年

8
@Alex哦,我明白了。好。从这个角度看,我认为这并不奇怪。仅从“回头看,也许这不是一个好主意”的角度来看。猜猜我应该更清楚了:)
harold 2012年

9

它只是节省了指令和指令集的空间。您可以使用现有的(32位)指令将小的立即数移到64位寄存器中。

MOV RAX, 42MOV EAX, 42可以重用时,它还使您不必为编码8个字节的值。

对于8位和16位操作(因为它们较小),此优化并不那么重要,并且在那里更改规则也会破坏旧代码。


7
如果是正确的话,对它进行符号扩展而不是0扩展是否更有意义?
Damien_The_Unbeliever 2012年

16
即使在硬件中,符号扩展也较慢。零扩展可以与产生下半部分的任何计算并行进行,但是只有在计算了下半部分(至少是其符号)之后才能进行符号扩展。
杰里·科芬

13
另一个相关的技巧是使用,XOR EAX, EAX因为XOR RAX, RAX需要REX前缀。
尼尔

3
@Nubok:当然,他们可以添加带有立即参数的movzx / movsx编码。在大多数情况下,将高位清零更为方便,因此您可以将值用作数组索引(因为有效地址中的所有reg必须具有相同的大小:[rsi + edx]不允许)。当然,避免错误的依赖关系/部分寄存器停顿(另一个答案)是另一个主要原因。
彼得·科德斯

4
并且在那里更改规则也会破坏旧代码。 旧代码无论如何都不能在64位模式下运行(例如1字节的inc / dec是REX前缀);这无关紧要。清除x86疣的原因是,长模式和兼容/旧模式之间的差异较小,因此,根据模式对不同解码的指令更少。AMD不知道AMD64是否会流行,并且不幸的是非常保守,因此需要更少的晶体管来支持。从长远来看,如果编译器和人员必须记住哪些事情在64位模式下工作会有所不同,那就很好了。
彼得·科德斯

1

如果没有零扩展到64位,则意味着从中读取一条指令rax对其rax操作数(写到eax该指令的指令和写rax在其之前的指令)具有2个依赖项,这意味着1)ROB必须具有用于一个操作数具有多个依赖关系,这意味着ROB将需要更多的逻辑和晶体管,并占用更多的空间,并且等待不必要的第二个依赖关系(可能会花费很多时间来执行)时,执行速度会变慢;或替代2),我猜这是16位指令发生的情况,分配阶段可能会停顿(即,如果RAT具有用于ax写的有效分配,并且eax出现了读取,则停顿直到ax写退休)。

mov rdx, 1
mov rax, 6
imul rax, rdx
mov rbx, rax
mov eax, 7 //retires before add rax, 6
mov rdx, rax // has to wait for both imul rax, rdx and mov eax, 7 to finish before dispatch to the execution units, even though the higher order bits are identical anyway

不为零扩展的唯一好处是确保包含较高的位rax,例如,如果它最初包含0xffffffffffffffffff,结果将为0xffffffff00000007,但是ISA很少有理由为此付出保证,并且实际上更可能需要零扩展的好处,因此它节省了额外的代码行mov rax, 0。通过确保将其始终零扩展为64位,编译器可以牢记此公理,而in中mov rdx, rax,则rax仅需等待其单个依赖关系即可,这意味着它可以更快地开始执行并退出,从而释放执行单元。此外,它还允许更高效的零成语,例如xor eax, eax零,rax而无需REX字节。


Skylake上的Partial标志至少可以通过为CF与任何SPAZO提供单独的输入来工作。(所以cmovbe是2,但是cmovb是1)。但是,没有执行任何部分寄存器重命名的CPU会按照您的建议进行操作。相反,如果将部分reg与完整reg分开重命名(即“脏”),则它们将插入合并的uop。请参阅为什么GCC不使用部分寄存器?Haswell / Skylake上的部分寄存器的性能如何?编写AL似乎对RAX有错误的依赖性,而AH则不一致
Peter Cordes

P6系列CPU停顿了约3个周期以插入合并的uop(Core2 / Nehalem),或更早的P6系列CPU(PM,PIII,PII,PPro)停滞了(至少?)约6个周期。也许就像您在2中所建议的那样,等待完整的reg值通过回写到永久/体系结构寄存器文件中而可用。
Peter Cordes

@PeterCordes哦,我知道至少要合并部分标志位的uops。很有道理,但我忘记了它是如何工作的。它单击一次,但我忘了记笔记
Lewis Kelsey,

@PeterCordes microarchitecture.pdf:不过,This gives a delay of 5 - 6 clocks. The reason is that a temporary register has been assigned to AL to make it independent of AH. The execution unit has to wait until the write to AL has retired before it is possible to combine the value from AL with the value of the rest of EAX我找不到用于解决此问题的“合并uop”的示例,对于部分国旗摊位来说也一样
Lewis Kelsey,

是的,早期的P6停滞不前,直到回写为止。Core2和Nehalem在之前/之后插入合并的uop?仅使前端停顿了较短时间。Sandybridge插入合并的uops而不会停顿。(但是AH合并必须单独发出一个周期,而AL合并可以是整个组的一部分。)Haswell / SKL根本没有将RAX与RAX分开重命名,所以mov al, [mem]微融合负载+ ALU-合并,仅重命名AH,而AH合并的uop仍然单独发出。这些CPU中的部分标志合并机制各不相同,例如Core2 / Nehalem仍然只是为部分标志而停顿,这与part-reg不同。
彼得·科德斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.