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

时间:2012-06-24 11:40:16

标签: assembly x86 x86-64 cpu-registers zero-extension

x86-64 Tour of Intel Manuals中,我读了

  

也许最令人惊讶的事实是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汇编的怪癖)。

4 个答案:

答案 0 :(得分:74)

我不是AMD或为他们说话,但我会以同样的方式做到这一点。因为将高半部分归零不会对先前的值产生依赖性,所以cpu必须等待。如果没有这样做,寄存器重命名机制基本上会被打败。这样,您就可以在64位模式下编写快速32位代码,而无需始终明确地断开依赖关系。如果没有这种行为,64位模式下的每一条32位指令都必须等待之前发生的事情,即使很高的部分几乎不会被使用。

16位指令的行为很奇怪。依赖性疯狂是现在避免使用16位指令的原因之一。

答案 1 :(得分:9)

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

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

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

答案 2 :(得分:2)

从硬件的角度来看,更新半个寄存器的能力一直有些昂贵,但在最初的 8088 上,允许手写汇编代码将 8088 视为具有两个非堆栈相关的16 位寄存器和 8 个 8 位寄存器,6 个与堆栈无关的 16 位寄存器和零 8 位寄存器,或其他 16 位和 8 位寄存器的中间组合。这样的用处值得付出额外的代价。

当 80386 添加 32 位寄存器时,没有提供仅访问寄存器上半部分的设施,但是像 ROR ESI,16 这样的指令足够快,仍然可以保持价值ESI 中的两个 16 位值并在它们之间切换。

随着向 x64 架构的迁移,增加的寄存器集和其他架构增强功能减少了程序员将最大量的信息压缩到每个寄存器中的需要。此外,寄存器重命名增加了进行部分寄存器更新的成本。如果代码要执行以下操作:

    mov rax,[whatever]
    mov [something],rax
    mov rax,[somethingElse]
    mov [yetAnother],rax

寄存器重命名和相关逻辑可以让 CPU 记录从 [whatever] 加载的值需要写入 something 的事实,然后——只要最后一个两个地址是不同的——允许加载 somethingElse 并存储到 yetAnother,而不必等待实际从 whatever 读取数据。但是,如果第三条指令是 mov eax,[somethingElse,并且指定为不影响高位,则第四条指令在第一次加载完成之前无法存储 RAX,甚至允许加载 {{1} } 发生会很困难,因为处理器必须跟踪这样一个事实:虽然下半部分可用,但上半部分不可用。

答案 3 :(得分:1)

如果没有零扩展到64位,则意味着从rax读取的一条指令对其rax操作数(写入eax的指令和写入到rax之前),这意味着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的高阶位,例如,如果它最初包含0xffffffffffffffff,则结果将是0xffffffff00000000000007,但是ISA很少需要这样做这种保证要付出如此大的代价,而且实际上更需要零扩展的好处,因此可以节省额外的代码行mov rax, 0。通过确保将其始终零扩展为64位,编译器可以牢记这一公理,而在mov rdx, rax中,rax仅需等待其单个依赖项,这意味着它可以更快地开始执行,并且退休,释放执行单位。此外,它还允许更有效的零成语,如xor eax, eax到零rax,而无需REX字节。