我学过一地址、二地址和三地址指令,但现在我想知道,x86使用哪种地址指令?
我学过一地址、二地址和三地址指令,但现在我想知道,x86使用哪种地址指令?
[rdi + rax*4]
这样的寻址模式。(有些指令可以有两个内存操作数,其中一个或两个都是隐式的。参考:What x86 instructions take two (or more) memory operands?)add eax, edx
,它执行eax+=edx
。
一些真正的单操作数ALU指令(没有隐含的其他操作数),例如inc
/dec
,neg
,not
,它们是对隐含的1进行加/减或从0处减去或与-1进行异或的快捷方式(有些具有不同的FLAGS语义)。还有bswap
。此外,具有隐含1计数的移位/旋转指令基本上是单操作数,有些汇编器确实允许您编写shr %eax
。
遗留的x87 FP代码使用带有x87堆栈的单操作数指令,例如faddp st1
,其中x87堆栈的顶部(st0
)是一个隐含的操作数。还有一些零操作数指令,例如只在st0
上隐式操作的fchs
。(SSE2是x86-64的基线,因此x87不再广泛使用。)
现代FP代码使用SSE/SSE2 2操作数指令,例如addsd xmm0,xmm1
或3操作数AVX编码,例如vaddsd xmm2, xmm0, xmm1
有0、1、2、3甚至4个显式操作数的x86指令。
有多种指令格式,但显式寄存器/内存操作数通常编码在跟随操作码字节的ModR/M字节中。(x86-64 instruction encoding on osdev提供了详细的细节和图示)。它有3个字段:
reg
、寄存器间接[reg]
、[reg+disp8]
、[reg+disp32]
),存在2位模式(带有位移比特的模式表示在ModR/M字节后跟随这些字节)。r/m, immediate
指令中,用作额外的操作码比特位,例如用于选择移位/旋转的类型。)大多数指令至少有2种编码方式,寄存器/内存目的地或寄存器/内存源。如果您想要的操作数都是寄存器,则可以使用任何一个操作码,即add r/m32, r32
或add r32, r/m32
。(一些汇编程序具有语法来让您选择非默认编码。理论上,汇编程序/编译器可以使用这些选择作为水印来显示哪个工具生成了它。)
reg
字段用作额外的操作码位,因此您仍然只能像add eax, 123
那样获得2个操作数。唯一的例外是186添加的imul
的立即形式,例如imul eax, [rdi + rbx*4], 12345
。它没有与其他立即指令共享编码空间,而是在ModR/M中具有一个寄存器目的地和一个r/m源,以及由操作码隐含的立即操作数。reg
字段作为额外的操作码位,但没有立即操作数。例如neg r/m32
、not r/m32
、inc r/m32
,或者按一个隐含1(而不是cl
或立即数)进行移位的shl
/shr
/rotate编码。因此,不幸的是,您不能复制和移位(直到BMI2)。
有一些特殊情况的编码可以提高代码密度,例如针对push rax
/push rdx
的单字节编码,将reg
字段打包到操作码字节的低3位中。在16/32位模式下,任何寄存器都可以使用单字节编码的inc
/dec
。但是在64位模式下,这些0x4?
的代码被用作REX前缀,扩展了reg
和r/m
字段,提供了16个体系结构寄存器。
有一些或所有隐含操作数的指令,例如movsb
将一个字节从[rsi]
复制到[rdi]
,并可与rep
前缀一起使用以重复rcx
次。
或者mul ecx
执行edx:eax = eax * ecx
。一个显式源操作数,一个隐式源操作数和两个隐式目标寄存器。div
/idiv
类似。
至少有1个显式寄存器/内存操作数的指令对其使用ModR/M编码,但是零显式操作数(如movsb
或cdq
)没有ModR/M字节。它们只有操作码。有些指令根本没有操作数,甚至没有隐式操作数,例如mfence
。
立即操作数不能通过ModR/M信号传递,只能由操作码本身指示,因此push imm32
或push imm8
具有自己的操作码。隐含目的地(存储在[rsp]
处的内存,以及RSP本身被更新为rsp-=8
)。
LEA是一种解决方案,可以给x86提供3操作数移位和加法,例如lea eax,[rdi + rdi * 2 + 123]
可以在一条指令中实现 eax = rdi * 3 + 123
。请参见如何在不是地址/指针的值上使用LEA? 目标寄存器编码在ModR/M的reg
字段中,两个源寄存器编码在寻址模式中。(涉及SIB字节,其存在由ModR/M字节使用编码表示,否则将意味着基础=RSP)。
bzhi eax,[rsi],edx
或vaddps ymm0,ymm1,[rsi]
。(对于许多指令,第二个源是可选的内存,但对于某些指令而言,第一个源是可选的。)
第三个操作数被编码在2或3字节的VEX前缀中。
有一些三操作数非VEX指令,例如SSE4.1变量混合指令,如vpblendvb xmm1, xmm2/m128, <XMM0>
,其中XMM0是使用该寄存器的隐式操作数。
AVX版本使其非破坏性(在VEX前缀中编码了一个单独的目标),并且使混合控制操作数显式(编码在1字节立即数的高4位中)。 这给我们带来了一个具有4个显式操作数的指令,VPBLENDVB xmm1,xmm2,xmm3/m128,xmm4
。
x86架构非常复杂,并且已经进行了多次扩展,但典型的整数代码主要使用双操作数指令,同时还有很多LEA指令用于节省指令。
0F AF imul r,r / m
不是在386上才开始出现的吗,不是在186上吗?您的 https://ulukai.org/ecm/insref.htm 和当前的 https://nasm.us/doc/nasmdocb.html 都这么说。http://bitsavers.trailing-edge.com/components/intel/80186/210911-001_iAPX86_88_186_188_Programmers_Reference_1983.pdf 只提到了即时imul,并没有提到2操作数形式(奇怪的是单操作数立即数,与另一个186手册不同)。当您说2操作数时,您是否计算了“imul eax,123”这种形式,其中汇编器允许您省略第一个源,如果它与目标相同? - Peter Cordessub
。(以及not
/ neg
的2操作数版本)。https://www.intel.com/content/www/us/en/developer/articles/technical/advanced-performance-extensions-apx.html - undefined