TL;DR:尽可能使用cltq(又名cdqe),因为它比完全等效的movslq %eax, %rax 短一个字节。这是一个非常小的优势(所以不要为了实现这一点而牺牲任何其他东西),但如果您想要签名扩展它,请选择 eax。
这主要与编译器编写器相关(编译有符号整数循环计数器索引数组);诸如每次迭代都对循环计数器进行符号扩展之类的事情仅在编译器无法利用带符号溢出作为未定义行为来避免它时才会发生。人类程序员只需要决定什么是有符号的还是无符号的来保存指令。
(使用movsx / movslq 符号扩展到不同的寄存器可以避免延长32位值的依赖链,如果它在循环中更新,则相关。)
相关:在 RAX (cltq) 内或从 EAX 到 EDX:EAX (@ 987654343@),相当于movsx/movs?t?:What does cltq do in assembly?。
历史
实际上,MOVSX 的 32->64 位形式(在 AT&T 语法中称为 movslq)是 AMD64 的新形式。 Intel 语法助记符实际上是MOVSXD。操作码是63 /r(所以它是 3 个字节,包括必要的 REX 前缀,而 8->64 或 16->64 MOVSX 是 4 个字节)。 AMD 重新利用了 ARPL 的操作码,这在 64 位模式下不存在。
要了解历史,请记住当前的 x86 并不是一次性设计的。首先是 16 位 8086,根本没有 MOVSZ/MOVZX,只有 CBW 和 CWD。然后 386 添加了 MOVS/ZX(以及更广泛的 CBW/CWD 版本,用于在 eax 或 edx 中进行符号扩展)。然后 AMD 将所有这些扩展到 64 位。
现有 MOVSX 操作码的 REX 版本仍然具有 8 位或 16 位源,但符号一直扩展到 64 位,而不是仅 32 位。操作数大小前缀允许您编码 movsbw,也就是 movsx r16, r/m8。 IDK 如果同时使用操作数大小前缀和 REX.W 会发生什么。或者,如果您在 MOVSX 的 16 位源格式中使用操作数大小的前缀,会发生什么情况。可能这只是编码 MOV 的一种昂贵方式,例如使用不带 REX 前缀的63 /r(英特尔的 insn 设置手册不建议这样做)。
cltq (aka CDQE) 是使用 REX.W 前缀扩展现有 cwtl (aka CWDE) 以将操作数大小提升到 64 位的明显方法。它的原始形式 cbtw(又名 CBW)在 8086 中,早于 MOVSX,并且是符号扩展任何东西的唯一合理方式。由于立即数>1 were a 186 feature 的移位,最不坏的其他选项似乎是 mov ah, al / mov cl, 7 / sar ah, cl 将符号位广播到所有位置。
另外,不要将 cwtl 与 cwtd 混淆(aka CWD:将 ax 符号扩展为 dx:ax,例如为 idiv 设置)。
这里的 AT&T 助记符非常糟糕。 l 与 d,真的吗?英特尔助记符的末尾都有 e 用于在 rax 中扩展的那些,而不是用于扩展到 rdx (部分)的那些。除了 CBW,当然这会将 al 扩展到 ax,因为即使 8086 也有 16 位寄存器,所以从不需要在 dl:al 中存储 16 位值。 idiv r/m8 使用 ax 作为源 reg,而不是 dl:al(并将结果放入 ah, al))。
裁员
是的,这是 x86 汇编语言中的众多冗余之一。例如sub eax,eax 与 xor eax,eax 相比,rax 为零。 (mov eax,0 并不是完全多余的,因为它不会影响标志。如果您将诸如此类的细微差异包括为冗余,甚至包括在不同执行端口上运行的指令,则有很多方法可以做一些事情。)。
如果我有机会修改 x86-64 ISA,我可能会给 MOVZX 和 MOVSX 单字节操作码(而不是 0F XX 两字节转义操作码),至少是 8 位源版本。所以movsx eax, byte [mem] 会和mov al, [mem] 一样紧凑。 (它们在 Intel CPU 上的性能已经相同:完全在加载端口中处理,没有 ALU uop)。大多数真实代码无法利用[u]int16_t 数组来提高缓存密度,所以我认为从word 到dword 或qword 的movs/zx 比较少见。或者也许有足够的宽字符代码来证明MOVZX r32/r64, r/m16 的较短操作码是合理的。为了腾出空间,我们可以完全放弃 CBW / CWDE / CDQE 操作码。我可能会保留 CWD / CDQ / CQO 作为 idiv 的有用设置,它没有等效的单指令。
实际上,可能具有更少的单字节操作码和更多的转义前缀会更有用(例如,常见的 SSE2 insn 可以是 2 个操作码字节 + ModRM,而不是通常的 3 或 4 个操作码字节)。在高性能循环中,指令解码的瓶颈较少。但是,如果 x86-64 机器代码与 32 位相差太大,我们需要额外的解码晶体管。现在可能没问题,因为功率限制已经使dark silicon 成为一件事,因为内核永远不需要在打开其 64 位解码器的同时打开其 32 位解码器。 AMD 在设计 AMD64 时并非如此。 (错误,在 32 位和 64 位运行的逻辑线程之间的超线程交替循环会阻止您完全关闭,如果它们是分开的。)
代替 CDQ,我们可以制作两个操作数移位指令,具有非破坏性目标,因此sar edx, eax, 31 将在 3 个字节中执行 CDQ。删除一字节 xchg-with-eax 操作码(0x90 xchg eax,eax NOP 除外)将为 sar, shr, shl 释放大量编码空间,而不需要 ModRM 的 Reg 字段作为额外的操作码位。当然,还要移除不影响标志的特殊情况 shift_count=0 以消除对 FLAGS 的输入依赖)。
(我也将setcc r/m8 更改为setcc r/m32。或者setcc r32/m8。(内存dst 无论如何都使用单独的ALU uop,因此它可以解码为setcc tmp32 并存储其中的低8)。它几乎总是用于对目的地进行异或归零,您必须在它与标志设置之间进行权衡。)
AMD 有机会(部分)使用 AMD64 做到这一点,但选择保守地共享尽可能多的指令解码晶体管。 (不能因此而责怪他们,但不幸的是,政治/经济环境导致 x86 在可预见的未来失去了放弃其一些遗留包袱的唯一机会。)这也意味着修改代码生成/分析软件的工作更少,但与可能使每个 x86-64 CPU 运行得更快并拥有更小的二进制文件相比,这是一次性成本和小土豆。
另请参阅x86 标签 wiki 以获取更多链接,包括 this old appendix from the NASM manual 记录何时引入每条指令的每种形式。
相关:MOVZX missing 32 bit register to 64 bit register。