您可以使用lea
简单的寄存器寻址模式作为较慢的模式mov
(没有移动消除,并且在 Ice Lake 之前在 Intel 上运行较少的端口),尽管它仍然是一条指令。
# nasm -f elf64 foo.asm && objdump -drwC -Mintel foo.o
0000000000000000 <.text>:
0: 89 c8 mov eax,ecx
2: 8d 01 lea eax,[rcx] # in 64-bit code,
4: 67 8d 01 lea eax,[ecx] # don't use 32-bit address size
Run Code Online (Sandbox Code Playgroud)
除了速度较慢之外,某些寄存器(RSP/R12 和 RBP/R13)还需要额外的代码大小。(我使用了 64 位操作数大小,因此它们都需要 REX 前缀,例如 RSP 和 R12 机器代码排列起来,因为它们都需要使用 SIB 字节。
10: 48 89 fe mov rsi,rdi
13: 48 8d 37 lea rsi,[rdi]
16: 48 8d 34 24 lea rsi,[rsp]
1a: 49 8d 34 24 lea rsi,[r12]
1e: 48 8d 75 00 lea rsi,[rbp+0x0] # source had just [rbp]
22: 49 8d 75 00 lea rsi,[r13+0x0]
Run Code Online (Sandbox Code Playgroud)
当然,同样的事情也可以在其他模式下使用,用作lea eax, [ecx]
2 字节指令。(或者 32 位寄存器在 16 位模式下为 4 个字节。即使您只需要 16 位寄存器,16 位模式也需要除 [bx|bp] + [si|di 之外的源寄存器的 32 位地址大小] 因为 16 位寻址模式编码限制。)
push
/pop
也是一个选项,使得仅用 2 个字节的机器代码(而不是通常的 3 个字节)复制 64 位寄存器成为可能。
或者,如果您不关心源寄存器(实际上是移动而不是复制),则可以xchg
,当 EAX 是两个寄存器之一时,它的编码更短
30: 52 push rdx
31: 58 pop rax
32: 48 89 d0 mov rax,rdx
35: 48 8d 02 lea rax,[rdx]
38: 87 f1 xchg ecx,esi # opcode + ModRM form
3a: 91 xchg ecx,eax # EAX special case
Run Code Online (Sandbox Code Playgroud)
其他愚蠢的计算机技巧包括imul
评论中的双班建议
立即imul
为 1。
对于 16 位寄存器,可以使用计数 = 16 的SHLD或 SHRD。(x86 标量整数移位会屏蔽 64 位以外的任何操作数大小的计数& 31
,因此这无法移位 32 或 64 位完整寄存器的所有位,只能移位 16 位部分寄存器。并且 shld /shrd 是 386 中的新增内容,因此 16 位寄存器始终是“部分”的。)
40: 6b f1 01 imul esi,ecx,0x1
43: 66 0f a4 ce 10 shld si,cx,0x10 # SI = CX. CX unchanged.
48: 66 0f ac ce 10 shrd si,cx,0x10
4d: 0f a4 ce 20 shld esi,ecx,0x20 # nope, equivalent to a shift by 0
Run Code Online (Sandbox Code Playgroud)
或者,如果您想考虑多个指令,您可以像 Yuri 建议的那样将目的地和add
、or
或 异或xor
归零。+
零是、|
和的单位元^
。
效率更低的是将 AND 转换为全 1,即 的单位元素&
。(错误地依赖于 EAX 的旧值,并且它不是异或归零,因此无法像 Sandybridge 系列 CPU 那样消除(无执行单元)。代码大小也更大)
or eax, -1
and eax, ecx
Run Code Online (Sandbox Code Playgroud)
(Godbolt编译器资源管理器 NASM源代码和反汇编,用于本答案中的反汇编)