如果一个寄存器可以是add 的操作数,或者用于寻址模式,那么它就是“通用”,而不是像FS 段寄存器或RIP 这样的寄存器。 GP 寄存器也称为“整数寄存器”,尽管其他类型的寄存器也可以保存整数。
在计算机架构中,CPU 在内部处理整数寄存器/指令与 FP/SIMD 寄存器/指令是很常见的。例如Intel Sandybridge-family CPUs 具有单独的物理寄存器文件,用于重命名 GP 整数与 FP/向量寄存器。这些被简单地称为整数与 FP 寄存器文件。 (其中 FP 是内核不需要保存/恢复以使用 GP 寄存器同时保持用户空间的 FPU/SIMD 状态不变的所有内容的简写。)FP 寄存器文件中的每个条目都是 256 位宽(到保存一个 AVX ymm 向量),但整数寄存器文件条目只需 64 位宽。
在重命名段寄存器 (Skylake does not) 的 CPU 上,我猜这将是整数状态的一部分,RFLAGS + RIP 也是如此。但是当我们说“整数寄存器”时,我们通常指的是一个通用寄存器。
“通用”在此用法中的意思是“数据或地址”,而不是像 m68k 这样的 ISA,其中您有 d0..7 数据寄存器和 a0..7 地址寄存器,其中 16 个都是整数寄存器。不管寄存器是如何正常使用的,通用是关于它如何可以使用。
每个寄存器对某些指令都有一些特殊性,除了一些使用 x86-64 添加的全新寄存器:R8-R15。这些并不能取消它们作为通用用途的资格 原始 8 的(低 16 位)可以追溯到 8086,即使在原始 8086 中也隐含使用它们。
对于 RSP,它对 push/pop/call/ret 是特殊的,因此大多数代码从不将它用于其他任何事情。 (在内核模式下,异步用于中断,所以你真的不能像在用户空间代码中那样将它存储在某个地方以获得额外的 GP 寄存器:Is ESP as general-purpose as EAX?)
但在受控条件(如无信号处理程序)中,您不必将 RSP 用于堆栈指针。例如您可以使用它在带有 pop 的循环中读取数组,例如in this code-golf answer。 (我实际上在 32 位代码中使用了 esp,但相同的区别:pop 在 Skylake 上比 lodsd 快,而两者都是 1 字节。)
每个寄存器的隐式使用和特殊性:
有关部分列表,另请参阅x86 Assembly - Why is [e]bx preserved in calling conventions?。
我主要将此限制为用户空间指令,尤其是现代编译器实际上可能从 C 或 C++ 代码发出的指令。对于有很多隐含用途的 reg,我并不想详尽无遗。
-
rax: 单操作数 [i]mul / [i]div / cdq / cdqe、字符串指令 (stos)、cmpxchg 等。以及用于许多立即指令(如 2)的特殊较短编码-byte cmp al, 1 或 5 字节 add eax, 12345(无 ModRM 字节)。另见codegolf.SE Tips for golfing in x86/x64 machine code。
还有xchg-with-eax,这是0x90 nop的来源(在nop成为x86-64中单独记录的指令之前,因为xchg eax,eax将eax零扩展为RAX,因此可以' t 使用0x90 编码。但xchg rax,rax 可以仍然组装成 REX.W=1 0x90。)
-
rcx:移位计数,rep-string 计数,the slow loop instruction
-
rdx:rdx:rax 用于除法运算,cwd / cdq / cqo 为它们设置。 rdtsc。 BMI2 mulx.
-
rbx: 8086 xlatb。 cpuid 使用所有四个 EAX..EDX。 486cmpxchg8b,x86-64 cmpxchg16b。大多数 32 位编译器将为 std::atomic<long long>::compare_exchange_weak 生成 cmpxchg8。 (纯加载/纯存储可以使用 SSE MOVQ 或 x87 fild/fistp,不过,如果针对 Pentium 或更高版本。)64 位编译器将使用 64 位 lock cmpxchg,而不是 cmpxchg8b。
一些 64 位编译器会为 atomic<struct_16_bytes> 生成 cmpxchg16b。 RBX 对原始 8 的隐式使用最少,但 lock cmpxchg16b 是少数几个会实际使用的编译器之一。
-
rsi/rdi:字符串操作,包括一些编译器有时内联的rep movsb。 (在某些情况下,gcc 还为字符串文字内联 rep cmpsb,但这可能不是最佳的)。
-
rbp: leave(仅比mov rsp, rbp / pop rbp 慢1 uop。gcc 实际上在带有帧指针的函数中使用它,而它不能只是pop rbp)。还有一个非常慢的enter,没人用过。
-
rsp:堆栈操作:push/pop/call/ret 和leave。 (和enter)。并且在内核模式(不是用户空间)中,硬件使用异步来保存中断上下文。这就是内核代码不能有红区的原因。
-
r11: syscall/sysret 使用它来保存/恢复用户空间的 RFLAGS。 (与 RCX 一起保存/恢复用户空间的 RIP)。
寻址模式编码特殊情况:
(另请参阅rbp not allowed as SIB base?,这只是关于寻址模式,我复制了这个答案的这一部分。)
rbp/r13 不能是没有位移的基址寄存器:该编码意味着:(在 ModRM 中)rel32(RIP 相对),或(在 SIB 中)disp32 没有基址登记。 (r13 在 ModRM/SIB 中使用相同的 3 位,因此该选择通过不让指令长度解码器查看the REX.B bit 来获得第 4 个基寄存器位来简化解码)。 [r13] 组装成 [r13 + disp8=0]。 [r13+rdx] 组装成 [rdx+r13](通过交换基数/索引来避免问题)。
rsp/r12 作为基址寄存器总是需要一个 SIB 字节。 (base=RSP 的 ModR/M 编码是用于发送 SIB 字节信号的转义码,同样,如果 r12 的处理方式不同,则更多的解码器将不得不关心 REX 前缀。
rsp 不能是索引寄存器。这使得编码[rsp] 成为可能,这比[rsp + rsp] 更有用。 (英特尔本可以为 32 位寻址模式设计 ModRM/SIB 编码(386 中的新功能),因此只有 base=ESP 才有可能使用无索引的 SIB。这将使[eax + esp*4] 成为可能,并且只排除[esp + esp*1/2/4/8] . 但这没有用,因此他们通过使 index=ESP 成为无索引的代码来简化硬件,无论基数如何。这允许两种冗余方式来编码任何基数或基数 + disp 寻址模式:有或没有 SIB。)
r12 可以是索引寄存器。与其他情况不同,这不会影响指令长度解码。此外,它不能像其他情况一样使用更长的编码来解决。 AMD 希望 AMD64 的寄存器集尽可能正交,因此他们会花费一些额外的晶体管来检查 REX.X 作为索引/无索引解码的一部分是有道理的。例如,[rsp + r12*4] 需要 index=r12,因此 r12 不完全通用会使 AMD64 成为更糟糕的编译器目标。
0: 41 8b 03 mov eax,DWORD PTR [r11]
3: 41 8b 04 24 mov eax,DWORD PTR [r12] # needs a SIB like RSP
7: 41 8b 45 00 mov eax,DWORD PTR [r13+0x0] # needs a disp8 like RBP
b: 41 8b 06 mov eax,DWORD PTR [r14]
e: 41 8b 07 mov eax,DWORD PTR [r15]
11: 43 8b 04 e3 mov eax,DWORD PTR [r11+r12*8] # *can* be an index
当所有寄存器可以用于任何事情时,编译器喜欢它,只限制少数特殊情况操作的寄存器分配。这就是寄存器正交性的含义。