我正在编写自己的汇编程序并尝试对 ADC 指令进行编码,我对立即值有疑问,尤其是在将 8 位值添加到 AX 寄存器时。
添加 16 位值时:adc ax, 0xff33
被编码为15 33 ff
正确的。但是如果adc ax, 0x33
被编码为有关系15 33 00
吗?
Nasm 将83 d0 33
其编码为显然是正确的,但我的方法也正确吗?
我正在编写自己的汇编程序并尝试对 ADC 指令进行编码,我对立即值有疑问,尤其是在将 8 位值添加到 AX 寄存器时。
添加 16 位值时:adc ax, 0xff33
被编码为15 33 ff
正确的。但是如果adc ax, 0x33
被编码为有关系15 33 00
吗?
Nasm 将83 d0 33
其编码为显然是正确的,但我的方法也正确吗?
x86 通常有超过 1 种有效的指令编码方式。例如,大多数op reg, reg
指令都可以选择通过op r/m, reg
或op reg, r/m
操作码进行编码。
是的,通常您希望汇编程序始终为指令选择最短的编码。NASM 甚至针对x86-64 将mov rax, 1
(7 字节mov r64, sign_extended_imm32
)优化为mov eax, 1
(5 字节),将操作数大小更改为使用零扩展,而不是 32 位立即数的显式符号扩展。
16 位的长度相等,但 32 位的操作数长度更短,因此它简化了您的代码以始终选择imm8
.
操作数大小为 32 位时,op eax, imm32
为 5 个字节,而op r/m32, imm8
仍然为 3 个字节。(不计算设置操作数大小或其他内容所需的任何前缀;两者都是相同的。)
如果需要操作数大小的前缀(例如,在 32 位模式下adc ax, 0x33
),使用带有操作数大小前缀的编码将在 Intel CPU 上adc ax/eax/rax, imm16/32/32
创建LCP 停顿(长度更改前缀意味着前缀更改其余部分的长度)指令。对于 imm8 编码不会发生这种情况,因为无论操作数大小如何,它仍然是 (prefix) + opcode + modrm + imm8。
请参阅 Agner Fog 的 microarch.pdf和x86 标签 wiki中的其他性能链接。另请参阅x86 指令编码如何选择与此重复的操作码,但这adc
是一种特殊情况。
在adc
/的特定情况下sbb
,避免ax, imm16
编码还有另一个优点:请参阅哪个英特尔微架构引入了 ADC reg,0 单 uop 特殊情况? 在通过 Haswell 的 Sandybridge 上,adc ax, 0
特殊情况下是单 uop 指令,而不是 3 输入 uop(ax,flags,immediate)的普通 2。
但是这种特殊的外壳不适用于 no-ModRM 短格式编码,因此 3-byteadc ax, imm16
仍然解码为 2 uops。只有imm8
表单的解码器在解码为单个微指令之前检查立即数是否为零。(而且它仍然不起作用adc al, imm8
。)
因此,尽可能始终选择符号扩展 imm8 也是最佳选择,即使在不需要操作数大小前缀的 16 位模式下,adc ax,0
也不会发生 LCP 停顿问题。
大多数汇编程序不提供覆盖以避免 no-ModRM 短格式。在设计它们时,除了有意延长指令以获得对齐而不在循环顶部或其他分支目标之前添加 NOP 之外,没有其他性能用例:在现代 x86 上可以使用哪些方法来有效地延长指令长度?
如果您正在设计一种新的 asm 语法,您可能会考虑允许使用 override 关键字对编码进行更多控制。对于现有设计,请查看 NASMstrict
和nosplit
关键字,以及 GAS 的{vex2}
、{vex3}
等{disp32}
“前缀”
nosplit
强制对 LEA 进行更长时间更有效的编码。GNU 汇编器 x86 指令后缀(如“mov.s”中的“.s”)如何工作?(GAS{disp32}
等,{load}
或{store}
选择您喜欢的编码op r/m, r
与编码中的哪一个。)op r, r/m
MOV moffs32 在 64 位模式下对地址进行符号或零扩展?在 64 位模式下,a32 mov eax, [0x123456]
使用 no-modrmmoffs
编码会导致 Intel CPU 上的 LCP 停止。对于绝对寻址,它比 modrm+SIB+disp32 短,但可能更慢。
mov rax,1
(5 字节)与mov rax, strict dword 1
(7 字节)与mov rax, strict qword 1
(10 字节imm64
编码)中的寄存器