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

wh6knrhe  于 2023-05-07  发布在  其他
关注(0)|答案(4)|浏览(212)

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组装的怪癖)。

wsewodh2

wsewodh21#

我不是AMD或为他们说话,但我也会这样做。因为将高半部分归零不会创建对前一个值的依赖性,CPU将不得不等待。如果不这样做,register renaming机制基本上就失败了。
通过这种方式,您可以在64位模式下使用32位值编写快速代码,而不必始终显式地中断依赖关系。如果没有这种行为,64位模式下的每个32位指令都必须等待之前发生的事情,即使高部分几乎从未被使用过。(使int成为64位会浪费缓存占用空间和内存带宽; x86-64 most efficiently supports 32 and 64-bit operand sizes
8位和16位操作数大小的行为是奇怪的。依赖疯狂是现在避免使用16位指令的原因之一。x86-64继承了8086(用于8位)和386(用于16位)的这一点,并决定让8位和16位寄存器在64位模式下以与32位模式相同的方式工作。
另请参阅Why doesn't GCC use partial registers?,了解真实的CPU如何处理对8位和16位部分寄存器的写入(以及随后对完整寄存器的读取)的实际细节。

i1icjdpr

i1icjdpr2#

它只是节省了指令和指令集的空间。您可以使用现有的(32位)指令将小的立即值移动到64位寄存器。
MOV EAX, 42可以重用时,它还可以使您不必为MOV RAX, 42编码8字节值。
这种优化对于8位和16位操作并不重要(因为它们更小),并且更改那里的规则也会破坏旧代码。

xmq68pz9

xmq68pz93#

如果零不扩展到64位,则意味着从rax阅读的指令将对其rax操作数具有2个依赖项(写入eax的指令和在其之前写入rax的指令),这将导致部分寄存器停顿,当存在3个可能的宽度时,这开始变得棘手,因此,它有助于raxeax写入完整寄存器,这意味着64位指令集不会引入任何新的部分重命名层。

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的高阶位,例如,如果它最初包含0xffffffffffffff,结果将是0xffffffff0000007,但伊萨几乎没有理由以这样的代价来保证这一点,并且更有可能的是,实际上需要更多的零扩展的好处。所以它节省了额外的代码行mov rax, 0。通过保证它将始终为零扩展到64位,编译器可以考虑这个 axios ,而在mov rdx, rax中,rax只需要等待它的单个依赖项,这意味着它可以更快地开始执行并退出,释放执行单元。此外,它还允许更有效的零习惯用法,如xor eax, eax到zerorax,而不需要雷克斯字节。

pnwntuvh

pnwntuvh4#

从硬件的Angular 来看,更新半个寄存器的能力总是有些昂贵,但在原始的8088上,允许手写汇编代码将8088视为具有两个非堆栈相关的16位寄存器和八个8位寄存器,六个非堆栈相关的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,甚至允许EAX的加载发生也是困难的,因为处理器必须跟踪下半部分可用,而上半部分不可用。

相关问题