Computer Systems A Programmer's Perspective (2nd Edition)的第 3 章提到这
cltq
相当于movslq %eax, %rax
.
为什么他们要创建一个新指令 ( cltq
) 而不是只使用movslq %eax,%rax
?这不是多余的吗?
Computer Systems A Programmer's Perspective (2nd Edition)的第 3 章提到这
cltq
相当于movslq %eax, %rax
.
为什么他们要创建一个新指令 ( cltq
) 而不是只使用movslq %eax,%rax
?这不是多余的吗?
TL;DR:尽可能使用cltq
(aka cdqe
),因为它比完全等效的 短一个字节movslq %eax, %rax
。这是一个非常小的优势(所以不要为了实现这一点而牺牲任何其他东西),但eax
如果你想要签名扩展它,请选择它。
这主要与编译器编写器相关(编译有符号整数循环计数器索引数组);诸如每次迭代都对循环计数器进行符号扩展之类的事情仅在编译器无法利用带符号溢出作为未定义行为来避免它时才会发生。人类程序员将决定什么是有符号的和无符号的来保存指令。
movsx
(用/符号扩展到不同的寄存器movslq
可以避免延长 32 位值的依赖链,如果它在循环中更新,则相关。)
相关:Intel 与 AT&T 助记符的完整对比,用于在 RAX ( ) 内符号扩展cltq
或从 EAX 到 EDX:EAX ( cltd
) 的指令的不同大小,等效movsx
/ movs?t?
:cltq 在汇编中做什么?.
实际上,MOVSX的 32->64 位形式(movslq
在 AT&T 语法中调用)是 AMD64 的新形式。 英特尔语法助记符实际上是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 进行编码的一种昂贵方式,例如63 /r
不使用 REX 前缀(英特尔的 insn 设置手册建议反对)。
cltq
(又名 CDQE)只是cwtl
使用 REX.W 前缀扩展现有(又名 CWDE)以将操作数大小提升到 64 位的明显方法。它的原始形式cbtw
(又名 CBW)是在 8086 中,早于 MOVSX,并且是符号扩展任何东西的唯一明智的方法。由于立即计数> 1的移位是 186 特征,因此最不坏的其他选项似乎是mov ah, al
//将符号mov cl, 7
位sar ah, cl
广播到所有位置。
另外,不要cwtl
与cwtd
(又名 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))。
裁员
是的,这是 x86 汇编语言中的众多冗余之一。例如sub eax,eax
,零 rax 与xor eax,eax
. (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 位相差太大,我们需要额外的解码晶体管。现在这可能没问题,因为功率限制已经使暗硅成为一件事,因为内核永远不需要其 32 位解码器与其 64 位解码器同时上电。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 以消除对标志的输入依赖性)。
(我也已更改setcc r/m8
为setcc r/m32
. 或者也许setcc r32/m8
. (内存 dst 无论如何都使用单独的 ALU uop,因此它可以解码为 setcc tmp32 并存储其中的低 8 位)。它几乎总是用于对目的地进行异或归零,并且您必须在这与标志设置之间进行权衡。)
AMD 有机会使用 AMD64 做(部分)这件事,但选择保守地共享尽可能多的指令解码晶体管。(不能因此而责怪他们,但不幸的是,政治/经济环境导致 x86 在可预见的未来失去了放弃其一些遗留包袱的唯一机会。)这也意味着修改代码生成/分析软件的工作更少,但与可能使每个 x86-64 CPU 运行得更快并拥有更小的二进制文件相比,这是一次性成本和小土豆。
另请参阅x86标记 wiki 以获取更多链接,包括NASM 手册中的旧附录,该附录记录了何时引入了每条指令的每种形式。