x86 cpu 有什么样的地址指令?

What kind of address instruction does the x86 cpu have?

我了解了一个地址、两个地址和三个地址指令,但现在我想知道,x86 使用什么样的地址指令?

x86 是 CISC register machine, where at most 1 operand for any instruction can be an explicit memory address instead of a register, using an addressing mode like [rdi + rax*4]. (There are instruction which can have 2 memory operands with one or both being implicit, though: )

典型的 x86 整数指令有 2 个操作数,都是显式的,例如 add eax, edx 执行 eax+=edx.

还有一些真正的 1 操作数 ALU 指令(没有隐含的其他操作数),如 inc/decnegnot,它们是 [=163 的快捷方式=] 的隐式 1,或 sub from 0,或与 -1 的 XOR(一些具有不同的 FLAGS 语义)。还有 bswap。此外,带有隐式 1 计数的 shift/rotate 指令基本上是 1 操作数,并且一些汇编程序允许您编写 shr %eax.

旧版 x87 FP 代码对 x87 堆栈使用 1 操作数指令,例如 faddp st1,其中 x87 堆栈的顶部 (st0) 是隐式操作数。还有一些 0 操作数指令,如 fchs 仅隐式地对 st0 进行操作。 (SSE2 是 x86-64 的基线,因此 x87 不再被广泛使用。)

现代 FP 代码使用 SSE/SSE2 2 操作数指令,如 addsd xmm0,xmm1 或 3 操作数 AVX 编码,如 vaddsd xmm2, xmm0, xmm1

有 0、1、2、3 甚至 4 个 显式 操作数的 x86 指令。

有多种指令格式,但显式 reg/memory 操作数通常编码在操作码字节后面的 ModR/M 字节中。 (x86-64 instruction encoding on osdev 有很好的细节和图表)。它有 3 个字段:

  • r/m 操作数的 2 位模式(直接寄存器 reg、间接寄存器 [reg][reg+disp8][reg+disp32])。带有位移位的模式表示这些字节跟在 ModR/M 字节之后。
  • 3 位 r/m 字段(寄存器直接或间接的寄存器编号,或者可以是转义码,这意味着在 ModRM 之后有一个 Scale/Index/Base SIB 字节可以编码缩放索引寻址r/m 操作数的模式)。有关特殊情况/转义码的详细信息,请参阅
  • 3 位寄存器字段,始终是寄存器编号。 (或在单操作数或r/m, immediate指令中,used as extra opcode bits,例如shifts/rotates选择哪种。)

大多数指令至少有 2 种编码可用,reg/memory 目标或 reg/memory 源。如果您想要的操作数都是寄存器,则可以使用任一操作码,add r/m32, r32add r32, r/m32。 (一些汇编器 。理论上,汇编器/编译器可以使用这些选择作为水印来显示它是由哪个工具生成的。)

普通指令也有直接源形式的其他操作码,但通常它们使用 ModR/M 中的 reg 字段作为额外的操作码位,因此您仍然只能得到 2 个操作数,例如 add eax, 123.一个例外是直接形式的 imul 添加 186,例如imul eax, [rdi + rbx*4], 12345。它没有与其他立即指令共享编码 space,而是在 ModR/M plus 中有一个寄存器 dst 和一个 r/m 源操作码。

一些单操作数指令使用相同的技巧,即使用 reg 字段作为额外的操作码位,但没有立即数。例如neg r/m32not r/m32inc r/m32shl/shr/按隐式 1(不是 cl 或即时)。所以不幸的是你不能复制和移动(直到 BMI2)。

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


也有带有部分或全部隐式操作数的指令,例如movsb,它将一个字节从[rsi]复制到[rdi],以及可以与 rep 前缀一起使用以重复 rcx 次。

或者 mul ecxedx:eax = eax * ecx。 1 个显式源操作数、1 个隐式源操作数和 2 个隐式目标寄存器。 div/idiv 相似。

具有至少 1 个显式 reg/mem 操作数的指令对其使用 ModR/M 编码,但具有零个显式操作数的指令(如 movsbcdq) have no ModR/M byte. They just have the opcode. Some instructions have no operands at all, not even implicit, like mfence.

立即操作数不能通过 ModR/M 发出信号,只能通过操作码本身发出信号,因此 push imm32 or push imm8 有自己的操作码。隐式目的地(内存在 [rsp],RSP 本身更新为 rsp-=8)。


LEA 是一种变通方法,它使 x86 3 操作数移位加法,就像 lea eax, [rdi + rdi*2 + 123] 在一条指令中执行 eax = rdi*3 + 123 一样。见目的寄存器编码在ModR/M的reg域,两个源寄存器编码在寻址方式。 (涉及一个 SIB 字节,它的存在由 ModR/M 字节使用编码表示,否则意味着 base = RSP)。


VEX 前缀(与 AVX 一起引入)提供 3 操作数指令,如 bzhi eax, [rsi], edxvaddps ymm0, ymm1, [rsi].(对于许多指令,第二个来源是一个可选的内存,但对于某些人来说它是第一个来源。)

第三个操作数在 2 或 3 字节 VEX 前缀中编码。


有一些 3 操作数非 VEX 指令,例如 SSE4.1 变量混合 vpblendvb xmm1, xmm2/m128, <XMM0> 其中 XMM0 是使用该寄存器的隐式操作数。

AVX 版本使其无损(在 VEX 前缀中编码了一个单独的目的地), 使混合控制操作数显式(在高 4 位中编码) 1 字节立即数)。 这给了我们一条带有 4 个显式操作数的指令,VPBLENDVB xmm1, xmm2, xmm3/m128, xmm4


x86 非常疯狂并且已经扩展了很多次,但是典型的整数代码主要使用 2 操作数指令,并加入了大量的 LEA 来保存指令。