如何取消存储在32位寄存器对中的64位整数?

问题描述:

我已经存储在EDX:EAX寄存器对一个64位整数。 我怎样才能正确否定多少?如何取消存储在32位寄存器对中的64位整数?

例如:123456789123-123456789123

+5

你忘了告诉你尝试过或考虑什么。有很多方法。首先,'-x = 0-x',所以你可以从0中减去。然后你也可以执行'-x = -1 * x'。做翻转所有的二进制补充公式也是一种选择。 – Jester

提出的想法编译器:编译在32位模式int64_t neg(int64_t a) { return -a; }。当然,要求编译器不同的方式都会有,或已经在EDX在内存中的初始值,在编译器的选择寄存器:EAX。查看所有三种方式on the Godbolt compiler explorer,与海湾合作委员会,铛输出ASM和MSVC(又名CL)。

当然也有很多方法来实现这一目标,但任何可能的顺序将需要某种形式携带的由低到高在某些时候,所以没有有效的办法来避免SBB或ADC。


如果该值开始在内存,或者你想保持在寄存器中的原始值,异或为零的目标,并使用SUB/SBB。 SysV x86-32 ABI在栈上传递参数,并在EDX:EAX中返回64位整数。这是clang3.9.1 -m32 -O3 does,为neg_value_from_mem

; optimal for data coming from memory: just subtract from zero 
    xor  eax, eax 
    xor  edx, edx 
    sub  eax, dword ptr [esp + 4] 
    sbb  edx, dword ptr [esp + 8] 

如果在寄存器的值,不需要就地,您可以使用NEG一个寄存器设置为0的结果 - 本身,设置CF如果输入不为零。即与SUB相同的方式。请注意,xor-zeroing is cheap,而不是延迟关键路径的一部分,所以这肯定比gcc的3指令序列(下图)要好。

;; partially in-place: input in ecx:eax 
    xor  edx, edx 
    neg  eax   ; eax = 0-eax, setting flags appropriately 
    sbb  edx, ecx ;; result in edx:eax 

Cla即使对于就地情况也这样做,即使这需要花费额外的mov ecx,edx。这对于具有零延迟mov reg,reg(Intel IvB +和AMD Zen)的现代CPU的延迟而言是最佳的,但对于融合域uops(前端吞吐量)或代码大小的数量而言并非如此。


海湾合作委员会的序列是有趣的,而不是完全明显。它保存了就地情况下的指令与叮当声,但是否则会更糟。

; gcc's in-place sequence, only good for in-place use 
    neg  eax 
    adc  edx, 0 
    neg  edx 
     ; disadvantage: higher latency for the upper half than subtract-from-zero 
     ; advantage: result in edx:eax with no extra registers used 

不幸的是,即使xor-zero + sub/sbb会更好,gcc和MSVC都会使用它。


对于什么样的编译器做一个更全面的了解,看看它们的输出为这些功能(on godbolt

#include <stdint.h> 

int64_t neg_value_from_mem(int64_t a) { 
    return -a; 
} 

int64_t neg_value_in_regs(int64_t a) { 
    // The OR makes the compiler load+OR first 
    // but it can choose regs to set up for the negate 
    int64_t reg = a | 0x1111111111LL; 
    // clang chooses mov reg,mem /or reg,imm8 when possible, 
    // otherwise  mov reg,imm32/or reg,mem. Nice :) 
    return -reg; 
} 

int64_t foo(); 
int64_t neg_value_in_place(int64_t a) { 
    // foo's return value will be in edx:eax 
    return -foo(); 
} 
+3

有趣的是只有GCC这样做,铛和ICC使用减为零。 – Jester

+0

为什么我们需要'adc edx,0'?如果*运算符*为* 0,'neg'操作只设置进位标志* –

+0

@NagyRobi:NEG的标志设置向后。它正在执行'edx = - (edx + CF)'而不是'edx = 0 - edx - CF',这就是为什么它使用ADC而不是SBB。 –