x86 CPU有哪些地址指令?

3

我学过一地址、二地址和三地址指令,但现在我想知道,x86使用哪种地址指令?


你所说的“地址”,是指“操作数”吗? - Sneftel
1
@Sneftel:是的,在抽象的ISA分类术语中,这意味着操作数。例如,在MIPS指令字中的5位寄存器字段是“地址”。(我不知道https://www.geeksforgeeks.org/computer-organization-instruction-formats-zero-one-two-three-address-instruction/是否好用,但那是他们使用的术语)。 - Peter Cordes
1个回答

7
x86是一种CISC寄存器机器,每个指令的最多一个操作数可以是明确的内存地址而不是寄存器,使用像[rdi + rax*4]这样的寻址模式。(有些指令可以有两个内存操作数,其中一个或两个都是隐式的。参考:What x86 instructions take two (or more) memory operands?
典型的x86整数指令有两个操作数,都是明确的,例如add eax, edx,它执行eax+=edx

一些真正的单操作数ALU指令(没有隐含的其他操作数),例如inc/decnegnot,它们是对隐含的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个字段:

  • 对于r/m操作数(寄存器直接reg、寄存器间接[reg][reg+disp8][reg+disp32]),存在2位模式(带有位移比特的模式表示在ModR/M字节后跟随这些字节)。
  • 3位r/m字段(用于寄存器直接或间接的寄存器编号,或者可以是一个转义码,表示在ModRM之后有一个Scale/Index/Base SIB字节,可对r/m操作数进行缩放索引寻址模式编码)。有关特殊情况/转义码的详细信息,请参见rbp not allowed as SIB base?
  • 3位reg字段,始终为寄存器编号。(在一操作数或r/m, immediate指令中,用作额外的操作码比特位,例如用于选择移位/旋转的类型。)

大多数指令至少有2种编码方式,寄存器/内存目的地或寄存器/内存源。如果您想要的操作数都是寄存器,则可以使用任何一个操作码,即add r/m32, r32add r32, r/m32。(一些汇编程序具有语法来让您选择非默认编码。理论上,汇编程序/编译器可以使用这些选择作为水印来显示哪个工具生成了它。)

常用指令还有其他立即源形式的操作码,但通常它们将ModR/M中的reg字段用作额外的操作码位,因此您仍然只能像add eax, 123那样获得2个操作数。唯一的例外是186添加的imul的立即形式,例如imul eax, [rdi + rbx*4], 12345。它没有与其他立即指令共享编码空间,而是在ModR/M中具有一个寄存器目的地和一个r/m源,以及由操作码隐含的立即操作数。
一些单操作数指令使用相同的技巧,使用reg字段作为额外的操作码位,但没有立即操作数。例如neg r/m32not r/m32inc r/m32,或者按一个隐含1(而不是cl或立即数)进行移位的shl/shr/rotate编码。因此,不幸的是,您不能复制和移位(直到BMI2)。

有一些特殊情况的编码可以提高代码密度,例如针对push rax/push rdx的单字节编码,将reg字段打包到操作码字节的低3位中。在16/32位模式下,任何寄存器都可以使用单字节编码的inc/dec。但是在64位模式下,这些0x4?的代码被用作REX前缀,扩展了regr/m字段,提供了16个体系结构寄存器。


有一些或所有隐含操作数的指令,例如movsb将一个字节从[rsi]复制到[rdi],并可与rep前缀一起使用以重复rcx次。

或者mul ecx执行edx:eax = eax * ecx。一个显式源操作数,一个隐式源操作数和两个隐式目标寄存器。div/idiv类似。

至少有1个显式寄存器/内存操作数的指令对其使用ModR/M编码,但是零显式操作数(如movsbcdq)没有ModR/M字节。它们只有操作码。有些指令根本没有操作数,甚至没有隐式操作数,例如mfence

立即操作数不能通过ModR/M信号传递,只能由操作码本身指示,因此push imm32push 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)。


VEX前缀(在AVX中引入)提供3操作数指令,例如bzhi eax,[rsi],edxvaddps 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指令用于节省指令。


“两个和三个操作数的IMUL”实际上是在186中引入的,而不是你所写的286。此外,您首先列出ModR/M字节的reg字段为“3位reg字段,始终为寄存器编号”,然后最终添加它可以根据需要扩展操作码。我会在列表条目中提到这一点。 - ecm
1
@ecm:关于/r的好建议,谢谢。是的,上次我们讨论到这个问题时我告诉你,我可能有几个答案包含了NASM旧附录中关于imul何时成为新的信息错误的内容。我会尝试搜索其他情况。 - Peter Cordes
1
@ecm:2-操作数的 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 Cordes
是的,我指的是三操作数指令的简写形式,其中目标和一个源使用相同的寄存器,最后一个操作数是立即数。然而,我应该在那方面更清楚;我实际上确实错过了没有立即操作数的两操作数形式。你说得对,那个是386+指令。 - ecm
1
@ecm:好的,很好。正如您所知,在机器代码中,imul-immediate始终具有3个操作数,这只是源级别的速记法。由于存在一个不同指令的2操作数形式,我认为最好只谈论实际机器代码操作数的数量,而不管您在源代码中如何编写它,至少在谈论哪些形式存在以及何时引入时。因为这是一个机器码问题。顺便说一句,我最终找到时间搜索和编辑提到“imul”和“286”的答案。到目前为止已经有大约十几个了。 - Peter Cordes
1
更新:APX将提供EVEX 3操作数编码的经典整数指令,如sub。(以及not / neg的2操作数版本)。https://www.intel.com/content/www/us/en/developer/articles/technical/advanced-performance-extensions-apx.html - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接