汇编cltq和movslq区别

assembly cltq and movslq difference

Computer Systems A Programmer's Perspective(第 2 版)第 3 章提到
cltq 等同于 movslq %eax, %rax.

为什么他们要创建一个新指令 (cltq) 而不是只使用 movslq %eax,%rax?这不是多余的吗?

TL;DR:尽可能使用 cltq(又名 cdqe),因为它比完全等价的 [=12= 短一个字节].这是一个非常小的优势(所以不要牺牲任何其他东西来实现这一点)但是如果你想对它进行大量签名扩展,请选择 eax

这主要与编译器编写者相关(编译有符号整数循环计数器索引数组);每次迭代都对循环计数器进行符号扩展之类的事情只会在编译器无法利用未定义行为的符号溢出来避免它时发生。人类程序员将决定什么是有符号的,什么是无符号的以保存指令。

(使用 movsx / movslq 将符号扩展到不同的寄存器可以避免延长 32 位值的依赖链,如果它在循环中更新则相关。)


相关:完成 运行-down 英特尔与 AT&T 助记符的不同大小 在 RAX 中进行符号扩展的指令(cltq ), 或者从 EAX 到 EDX:EAX (cltd), 等价于 movsx / movs?t?: What does cltq do in assembly?.


历史

实际上是 MOVSX (called movslq in AT&T syntax), is the new one, new with AMD64. The Intel-syntax mnemonic is actually MOVSXD 的 32->64 位形式。操作码是 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(Intel 的 insn set 手册建议不要这样做)。


cltqaka CDQE) is just the obvious way to extend the existing cwtl (aka CWDE) with a REX.W prefix to promote the operand-size to 64 bits. The original form of this, cbtw (aka CBW), was in 8086, predating MOVSX, and was the only sane way to sign-extend anything. Since shifts with immediate count>1 were a 186 feature,最不坏的其他选项似乎是 mov ah, al / mov cl, 7 / sar ah, cl 将符号位广播到所有位置.

此外,不要将 cwtlcwtd 混淆(aka CWD:将 ax 符号扩展到 dx:ax,例如为 idiv 设置)。

这里的 AT&T 助记符非常糟糕。 l 对比 d,真的吗? Intel 助记符的末尾都有 e,用于扩展到 rax 的助记符,而不是扩展到(部分)rdx 的助记符。 CBW 除外,但当然将 al 扩展到 ax,因为即使 8086 也有 16 位寄存器,所以从来不需要在 dl:al 中存储 16 位值。 idiv r/m8 使用 ax 作为源 reg,而不是 dl:al(并将结果放入 ah, al))。


redundancies

是的,这是 x86 汇编语言中的众多冗余之一。例如sub eax,eax 将 rax 与 归零。 (mov eax,0 并不完全是多余的,因为它不影响标志。如果您将像这样的细微差异视为冗余,甚至包括 运行 在不同执行端口上的指令,则有很多方法可以做一些事情。)。

如果我有机会修改 x86-64 ISA,我可能会给出 MOVZX 和 MOVSX 单字节操作码(而不是 0F XX 双字节转义操作码),至少 8 位-源版本。所以 movsx eax, byte [mem]mov al, [mem] 一样紧凑。 (它们在 Intel CPUs 上的性能已经相同:完全在加载端口处理,没有 ALU uop)。大多数实际代码都无法利用 [u]int16_t 数组来获得更高的缓存密度,因此我认为 movs/zx 从 word 到 dword 或 qword 比较少见。或者可能有足够的宽字符代码来证明 MOVZX r32/r64, r/m16 的较短操作码是合理的。为了腾出一些空间,我们可以完全删除 CBW / CWDE / CDQE 操作码。我可能会保留 CWD / CDQ / CQO 作为 idiv 的有用设置,它没有等效的单指令。

实际上,使用更少的单字节操作码和更多的转义前缀可能会更有用(例如,常见的 SSE2 insn 可以是 2 个操作码字节 + ModRM,而不是通常的 3 或 4 个操作码字节)。在高性能循环中,指令解码不是瓶颈,指令越短。但如果 x86-64 机器码与 32 位机器码差别太大,我们就需要额外的解码晶体管。既然功率限制已经使 dark silicon 成为现实,那可能没问题,因为内核永远不需要其 32 位解码器与其 64 位解码器同时开启。 AMD 设计 AMD64 时情况并非如此。 (错误,HyperThreading 在 32 位和 64 位逻辑线程之间的交替循环 运行ning 将阻止您完全关闭任何一个,如果它们是分开的。)

我们可以制作具有非破坏性目标的双操作数移位指令来代替 CDQ,因此 sar edx, eax, 31 将在 3 个字节中执行 CDQ。删除一字节 xchg-with-eax 操作码(0x90 xchg eax,eax NOP 除外)将为 sar, shr, shl 释放大量编码 space,而不需要 ModRM 的 Reg 字段作为额外的操作码位。当然,删除 shift_count=0 的不影响标志特例以消除对 FLAGS 的输入依赖性)。

(我也会将 setcc r/m8 更改为 setcc r/m32。或者可能 setcc r32/m8。(内存 dst 使用单独的 ALU uop,因此它可以解码为 setcc tmp32 和存储其中的低 8 位。它几乎总是用于对目的地进行异或归零,您必须兼顾它与标志设置。)

AMD 有机会用 AMD64 做到(部分)这个,但选择保守地共享尽可能多的指令解码晶体管。 (不能因此而责备他们,但不幸的是 political/economic 情况导致 x86 在可预见的未来失去了唯一的机会来放弃它的一些遗留包袱。)这也意味着修改代码生成/分析的工作更少软件,但与可能使每个 x86-64 CPU 运行 更快且二进制文件更小相比,这是一次性成本和小土豆。


另请参阅 tag wiki for more links, including this old appendix from the NASM manual 介绍每条指令的每种形式的记录。

相关:MOVZX missing 32 bit register to 64 bit register.