否定浮点变量的不同方法会生成不同的程序集

Different ways of negating a float variable generate different assembly

我有以下代码:

public static float F(float n) 
{
    return -n;
}

生成以下 asm:

Program.F(Single)
    L0000: push eax
    L0001: vzeroupper
    L0004: vmovss xmm1, [esp+8]
    L000a: vmovss xmm0, [Program.F(Single)]  ; remember this line
    L0012: vxorps xmm0, xmm0, xmm1           ; remember this line
    L0016: vmovss [esp], xmm0
    L001b: fld st, dword ptr [esp]
    L001e: pop ecx
    L001f: ret 4

另一方面,我有以下片段:

public static float G(float n) 
{
    return n * -1;
}

生成:

Program.G(Single)
    L0000: push eax
    L0001: vzeroupper
    L0004: vmovss xmm0, [esp+8]
    L000a: vmulss xmm0, xmm0, [Program.G(Single)] ; remember this line
    L0012: vmovss [esp], xmm0
    L0017: fld st, dword ptr [esp]
    L001a: pop ecx
    L001b: ret 4

问题

备注

注意是针对最后一个问题的。

我想基本上得到像汇编这样的“C”。例如:

float 
f(float n) {
        return -n; 
}

float
g(float n) {
        return n * -1;
}

生成:

f:
        xorps   xmm0, XMMWORD PTR .LC0[rip]
        ret
g:
        xorps   xmm0, XMMWORD PTR .LC0[rip]
        ret
.LC0:
        .long   -2147483648
        .long   0
        .long   0
        .long   0
public static unsafe float F(float n) 
{
    uint i = *(uint*)&n;
    
    i ^= 0x80000000;
    
    n = *(float*)&i;
    
    return n;
}

我得到了以下我认为效率更低的:

Program.F(Single)
    L0000: sub esp, 8
    L0003: vzeroupper
    L0006: mov eax, [esp+0xc]
    L000a: mov [esp], eax
    L000d: mov eax, [esp]
    L0010: xor eax, 0x80000000
    L0015: mov [esp], eax
    L0018: vmovss xmm0, [esp]
    L001d: vmovss [esp+0xc], xmm0
    L0023: vmovss xmm0, [esp+0xc]
    L0029: vmovss [esp+4], xmm0
    L002f: fld st, dword ptr [esp+4]
    L0033: add esp, 8
    L0036: ret 4

现在我们有了一个“高效”的汇编代码:

public static float F(float n) 
{
    return -n;
}
    
public static float G(float n) 
{
    return n * -1;
}
C.F(Single)
    L0000: vzeroupper
    L0003: vmovss xmm1, [C.F(Single)]
    L000b: vxorps xmm0, xmm0, xmm1
    L000f: ret

C.G(Single)
    L0000: vzeroupper
    L0003: vmulss xmm0, xmm0, [C.G(Single)]
    L000b: ret

但是我认为第一个和第二个问题是有效的:

Why do we have different ASM outputs here?

输出不同,原因很简单,编译器不像人类那样'think'而是遵循标准。

public static float F(float n) 
{
    return -n;
}

这意味着,要求编译器否定 n 和return 否定值。
这正是编译器所做的

vxorps xmm0, xmm0, xmm1    <--- change sign bit

public static float G(float n) 
{
    return n * -1;
}

这意味着执行乘法,而这正是编译器所做的

vmulss xmm0, xmm0, [C.G(Single)] <--- multiply

在你的世界里(-n) == (n * -1)但是编译器对此有不同的看法。而这两个表达方式并不相同。所以 (-n) != (n * -1) 和汇编输出是不同的。

比那更糟,因为 float/double 值 (a * b * c) != (c * b * a)...

嗯,至少默认情况下不是,你可以用特殊的编译器标志使它相等,抱歉不记得它们到底是什么了。