汇编语言 - 从 ecx 中减去 edx 并将结果放入 ebx
Assembly Language - Subtract edx from ecx and place result in ebx
如何在不更改任何其他寄存器的情况下执行此操作(也就是保持 ecx
和 edx
与以前相同)?
在 C++ 中,它将是这样的:
int ecx = 3;
int edx = 1;
int ebx = ecx - edx;
到目前为止,我已经这样做了:
mov ecx, 1
mov edx, 3
sub ecx, edx
mov ebx, ecx
您始终可以使用堆栈来保存寄存器:
push ecx
push edx
mov ecx, 1
mov edx, 3
sub ecx, edx
mov ebx, ecx
pop edx
pop ecx
使用 x86 风格的 2 操作数指令破坏它们的目的地,你总是可以模拟一个非破坏性的 3 操作数指令 mov
将一个操作数复制到目的地,然后 运行 对该目的地的破坏性指令。
# with ecx and edx holding your inputs (which I'm calling C and D).
mov ebx, ecx ; ebx = C
sub ebx, edx ; ebx = C - D
对于这种情况,您可以做到最好,因为您不需要破坏 ECX 和 EDX 中的值。
如果您 运行 可用寄存器不足,将 ECX 保存在堆栈上然后在 ECX 中生成 C - D
结果而不是新寄存器可能是一个不错的选择。
通常您可以在整个函数中对同一个变量使用同一个寄存器,但这不是必需的,有时也不是最佳选择。使用评论来跟踪事情。
编译器通常非常擅长寄存器分配,但它们的代码可能难以阅读,因为它们甚至不尝试与寄存器使用保持一致。对于非破坏性操作,他们通常会无缘无故地将结果放入新的寄存器中。不过,编译器输出通常是优化的良好起点。 (写一个做某事的小函数,看看它是如何编译的。或者用函数参数而不是常量作为输入用 C 编写你的整个东西,然后编译它。)
x86 有一些用于其他操作的复制和操作指令(不是 sub
),.
lea ebx, [ecx + ecx*4] ; ebx = C * 5
lea ebx, [ecx + ebx - 2] ; ebx = C + D - 2
x86寻址模式可以加减常量,但只能左移加寄存器。
immediate-operand form of imul
也是 3 操作数,用于乘法器,而您不能使用 1 或 2 个 LEA:
imul ebx, ecx, 0x01010101 ; ebx = cl repeated 4 times, if upper bytes were zero
与大多数直接操作数指令不同,imul
不会 overload the /r
field in the ModRM byte as extra opcode bits. So it has room to encode a register destination and a reg/mem source, because 186 将整个操作码字节专门用于它。
ISA 扩展,例如 BMI1 和 BMI2 添加了一些新的 3 操作数整数指令,例如 ANDN and SHRX.
andn ebx, ecx, edx ; ebx = (~C) & D ; BMI1
shrx ebx, edx, ecx ; ebx = D >> C ; BMI2
但它们并不是普遍可用的,只有 Haswell 和更高版本,以及 Ryzen。 (并且 Haswell/Skylake 的 Pentium/Celeron 版本在没有它们的情况下仍在销售,进一步延迟了它们成为基准的时间点。谢谢,英特尔。)
当然对于矢量指令,AVX 提供所有 SSE 指令的非破坏性版本。
movaps xmm2, xmm0 ; copy a whole register
subsd xmm2, xmm1 ; scalar double-precision FP subtract: xmm0-xmm1
vsubsd xmm3, xmm0, xmm1
或不太明显的用例
xorps xmm0, xmm0 ; zero the register and break any false dependencies
cvtsi2sd xmm0, eax ; convert to double-precision FP, with the upper element = 0
xorps xmm1, xmm1
cvtsi2sd xmm1, edx
对比AVX:
vxorps xmm1, xmm1,xmm1 ; xmm1 = all-zero
vcvtsi2sd xmm0, xmm1, eax
vcvtsi2sd xmm1, xmm1, edx
这将重复使用相同的置零寄存器作为合并目标以避免错误依赖(并使 128 位寄存器的高 64 位为零)。
如何在不更改任何其他寄存器的情况下执行此操作(也就是保持 ecx
和 edx
与以前相同)?
在 C++ 中,它将是这样的:
int ecx = 3;
int edx = 1;
int ebx = ecx - edx;
到目前为止,我已经这样做了:
mov ecx, 1
mov edx, 3
sub ecx, edx
mov ebx, ecx
您始终可以使用堆栈来保存寄存器:
push ecx
push edx
mov ecx, 1
mov edx, 3
sub ecx, edx
mov ebx, ecx
pop edx
pop ecx
使用 x86 风格的 2 操作数指令破坏它们的目的地,你总是可以模拟一个非破坏性的 3 操作数指令 mov
将一个操作数复制到目的地,然后 运行 对该目的地的破坏性指令。
# with ecx and edx holding your inputs (which I'm calling C and D).
mov ebx, ecx ; ebx = C
sub ebx, edx ; ebx = C - D
对于这种情况,您可以做到最好,因为您不需要破坏 ECX 和 EDX 中的值。
如果您 运行 可用寄存器不足,将 ECX 保存在堆栈上然后在 ECX 中生成 C - D
结果而不是新寄存器可能是一个不错的选择。
通常您可以在整个函数中对同一个变量使用同一个寄存器,但这不是必需的,有时也不是最佳选择。使用评论来跟踪事情。
编译器通常非常擅长寄存器分配,但它们的代码可能难以阅读,因为它们甚至不尝试与寄存器使用保持一致。对于非破坏性操作,他们通常会无缘无故地将结果放入新的寄存器中。不过,编译器输出通常是优化的良好起点。 (写一个做某事的小函数,看看它是如何编译的。或者用函数参数而不是常量作为输入用 C 编写你的整个东西,然后编译它。)
x86 有一些用于其他操作的复制和操作指令(不是 sub
),
lea ebx, [ecx + ecx*4] ; ebx = C * 5
lea ebx, [ecx + ebx - 2] ; ebx = C + D - 2
x86寻址模式可以加减常量,但只能左移加寄存器。
immediate-operand form of imul
也是 3 操作数,用于乘法器,而您不能使用 1 或 2 个 LEA:
imul ebx, ecx, 0x01010101 ; ebx = cl repeated 4 times, if upper bytes were zero
与大多数直接操作数指令不同,imul
不会 overload the /r
field in the ModRM byte as extra opcode bits. So it has room to encode a register destination and a reg/mem source, because 186 将整个操作码字节专门用于它。
ISA 扩展,例如 BMI1 和 BMI2 添加了一些新的 3 操作数整数指令,例如 ANDN and SHRX.
andn ebx, ecx, edx ; ebx = (~C) & D ; BMI1
shrx ebx, edx, ecx ; ebx = D >> C ; BMI2
但它们并不是普遍可用的,只有 Haswell 和更高版本,以及 Ryzen。 (并且 Haswell/Skylake 的 Pentium/Celeron 版本在没有它们的情况下仍在销售,进一步延迟了它们成为基准的时间点。谢谢,英特尔。)
当然对于矢量指令,AVX 提供所有 SSE 指令的非破坏性版本。
movaps xmm2, xmm0 ; copy a whole register
subsd xmm2, xmm1 ; scalar double-precision FP subtract: xmm0-xmm1
vsubsd xmm3, xmm0, xmm1
或不太明显的用例
xorps xmm0, xmm0 ; zero the register and break any false dependencies
cvtsi2sd xmm0, eax ; convert to double-precision FP, with the upper element = 0
xorps xmm1, xmm1
cvtsi2sd xmm1, edx
对比AVX:
vxorps xmm1, xmm1,xmm1 ; xmm1 = all-zero
vcvtsi2sd xmm0, xmm1, eax
vcvtsi2sd xmm1, xmm1, edx
这将重复使用相同的置零寄存器作为合并目标以避免错误依赖(并使 128 位寄存器的高 64 位为零)。