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

Nub*_*bok 97 x86 assembly x86-64 cpu-registers zero-extension

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

也许最令人惊讶的事实是,诸如MOV EAX, EBX自动将指令的高32位归零的指令RAX.

同一来源引用的英特尔文档(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
Run Code Online (Sandbox Code Playgroud)

不要表现出这种"奇怪"的行为,即eax的上层词被归零.

因此:引入这种行为的原因是什么?乍一看似乎不合逻辑(但原因可能是我习惯了x86-32汇编的怪癖).

har*_*old 82

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

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

  • 我不认为这很奇怪,我认为他们不想打破太多,并保持旧的行为. (8认同)
  • @Alex哦,我明白了.好.从这个角度来看,我认为这并不奇怪.只是从"回顾,也许这不是一个好主意" - 相关.猜猜我应该更清楚:) (8认同)
  • @Alex引入32位模式时,高部分没有旧行为.之前没有很高的部分..当然之后它再也无法改变了. (4认同)
  • 我解释你的"16位指令的行为是奇怪的",因为"在64位模式下16位操作数不会发生零扩展,这很奇怪".因此我的评论是在64位模式下保持相同的方式以获得更好的兼容性. (3认同)
  • 16 位命令的逻辑可以是“如果我们必须保持兼容性并因此依赖于先前寄存器值的位 16-31,则清除位 32-63 不会拯救我们。因此,完全忽略此清除。” 无论如何,这并不是 x86-64 最奇怪的地方。 (3认同)
  • 我说的是 16 位操作数,为什么在这种情况下最高位不会被清零。它们在非 64 位模式下不支持。并且它也保持在 64 位模式下。 (2认同)

Bo *_*son 10

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

它还使您不必编码8字节值MOV RAX, 42,何时MOV EAX, 42可以重复使用.

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

  • 即使在硬件中,符号扩展也较慢.零扩展可以与产生下半部分的任何计算并行完成,但是在计算下半部分(至少是符号)之前不能进行符号扩展. (16认同)
  • 另一个相关技巧是使用`XOR EAX,EAX`,因为`XOR RAX,RAX`需要一个REX前缀. (11认同)
  • 如果这是正确的,那么签署扩展而不是0扩展会不会更有意义? (6认同)
  • *更改规则也会破坏旧代码。* 旧代码无论如何都不能在 64 位模式下运行(例如,1 字节 inc/dec 是 REX 前缀);这无关紧要。*不*清除 x86 缺陷的原因是长模式和兼容/传统模式之间的差异较小,因此需要根据模式进行不同解码的指令更少。AMD 不知道 AMD64 会流行起来,不幸的是,它非常保守,因此需要更少的晶体管来支持。从长远来看,如果编译器和人类必须记住哪些东西在 64 位模式下工作方式不同,那就太好了。 (5认同)
  • @Nubok:当然,他们可以添加一个立即参数的movzx/movsx编码.大多数情况下,将高位置零会更方便*所以你可以使用一个值作为数组索引(因为所有regs必须在有效地址中具有相同的大小:`[rsi + edx]`isn'允许).当然,避免错误依赖/部分注册失速(另一个答案)是另一个主要原因. (3认同)
  • @Alex:并且签名扩展不是吗?两者都可以在硬件上非常便宜地完成. (2认同)
  • @Alex:不,不是.如果用软件完成它肯定会慢一些,但在硬件方面,它最坏的情况是花费更多的晶体管,在芯片上,现代CPU的尺寸和复杂性确实不是问题. (2认同)
  • @Nawas - `(rax)` 或 `[rax]` 取决于汇编器,类似于指针解引用,因此它会从 `rax` 中的地址加载一个值,并用加载的值替换该指针。 (2认同)
  • @Damien_The_Unbeliever:`mov r/m64, sign_extended_imm32`(7 字节)可使用带有 REX.W 前缀的`mov r/m32, imm32` 操作码(https://www.felixcloutier.com/x86/mov)。no-ModRM mov-to-reg 编码是 5 字节的 `mov r32,imm32` 或 10 字节的 `mov r64, imm64` 没有/有 REX.W。此外,我对 [MOVZX 缺少 32 位寄存器到 64 位寄存器](//stackoverflow.com/q/51387571) 的回答的最后一部分讨论了 x86-64 样式隐式零扩展与需要正确符号的 MIPS64 的优点- 32 位操作数大小的扩展输入/输出。 (2认同)

Lew*_*sey 6

如果没有零扩展到 64 位,则意味着读取的指令rax对其操作数有 2 个依赖性rax(写入的指令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
Run Code Online (Sandbox Code Playgroud)

非零扩展的唯一好处是确保包含 的高阶位rax,例如,如果它最初包含 0xffffffffffffffff,则结果将是 0xffffffff00000007,但 ISA 没有理由以如此大的代价做出此保证,并且更有可能的是,实际上需要更多零扩展的好处,因此它节省了额外的代码行mov rax, 0。通过保证它始终为零扩展到 64 位,编译器可以在 时牢记这一公理mov rdx, raxrax只需等待其单个依赖项,这意味着它可以更快地开始执行并退出,从而释放执行单元。此外,它还允许更有效的零习惯用法,例如xor eax, eaxrax,而不需要 REX 字节。


归档时间:

查看次数:

24417 次

最近记录:

5 年,11 月 前