我正在编写自己的汇编器并尝试对ADC指令进行编码,我对立即数有疑问,尤其是在将8位值添加到AX寄存器中时。
当添加16位值时:adc ax, 0xff33
被编码为15 33 ff
,这是正确的。
但是如果将adc ax, 0x33
编码为15 33 00
会很重要吗?
Nasm将此编码为83 d0 33
,这显然是正确的,但是我的方法也正确吗?
答案 0 :(得分:3)
x86通常具有一种以上的有效指令编码方式。例如大多数op reg, reg
指令可以选择通过op r/m, reg
或op reg, r/m
操作码进行编码。
是的,通常,您希望汇编程序始终选择指令的最短编码。对于x86-64,NASM甚至将mov rax, 1
(mov r64, sign_extended_imm32
的7个字节)优化为mov eax, 1
(5个字节),将操作数大小更改为使用零扩展来写入32位注册,而不是对32位立即数进行显式符号扩展。
对于16位来说,它的长度是相等的,但是对于32位操作数大小来说,它的长度较短,因此它简化了您的代码,始终选择imm8
。
操作数大小为32位时,op eax, imm32
为5字节,而op r/m32, imm8
仍为3字节。 (不计算设置操作数大小或其他内容所需的任何前缀;这两个前缀都是相同的。)
如果需要操作数大小的前缀(例如在adc ax, 0x33
的32位模式下),则将adc ax/eax/rax, imm16/32/32
编码与操作数大小的前缀一起使用将在Intel上创建 LCP停顿CPU (更改长度的前缀表示前缀会更改指令的 rest 的长度。对于imm8编码,这不会发生,因为它仍然是(前缀)+操作码+ modrm + imm8,无论操作数大小如何。
请参见Agner Fog's microarch.pdf和x86 tag wiki中的其他性能链接。另请参阅x86 instruction encoding how to choose opcode,该副本与adc
是特例。
在adc
/ sbb
的特定情况下,避免使用ax, imm16
编码还有另一个优点:请参见Which Intel microarchitecture introduced the ADC reg,0 single-uop special case?在Hasy上通过Haswell,adc ax, 0
是特殊情况下的单uup指令,而不是3输入uop(ax,标志,立即数)的普通2。
但是,这种特殊的大小写不适用于no-ModRM短格式编码,因此3字节的adc ax, imm16
仍可解码为2微秒。仅imm8
格式的解码器在解码为单个uop之前会检查立即数是否为零。 (并且它仍然不适用于adc al, imm8
。)
因此,即使在16位模式下,adc ax,0
都不需要操作数大小的前缀,并且始终尽可能选择sign-extended-imm8对此也是最佳的选择。因此不会发生LCP停转的问题。
大多数汇编程序都不提供重写,以避免no-ModRM缩写形式。在设计它们时,除了在循环顶部或其他分支目标之前不加任何NOP的情况下,没有故意延长指令以获取对齐的性能用例:What methods can be used to efficiently extend instruction length on modern x86?
如果您要设计一种新的asm语法,则可以考虑允许使用override关键字对编码进行更多控制。对于现有设计,请查看NASM的strict
和nosplit
关键字,以及GAS的{vex2}
,{vex3}
,{disp32}
等“前缀”
nosplit
强制采用更长,更有效的LEA编码。 How do GNU assembler x86 instruction suffixes like ".s" in "mov.s" work?(GAS {disp32}
等,以及{load}
或{store}
来选择op r/m, r
与{{1} }编码。)
Sign or Zero Extension of address in 64bit mode for MOV moffs32?在64位模式下,使用无现代op r, r/m
编码的a32 mov eax, [0x123456]
会导致Intel CPU上的LCP停顿。绝对寻址比modrm + SIB + disp32短,但可能会更慢。
moffs
(5字节)与mov rax,1
(7字节)与mov rax, strict dword 1
(10字节mov rax, strict qword 1
编码)