繁体   English   中英

为什么 gcc 仅在 SS/SD 指令中使用较低值时不将 XMM 寄存器的较高值归零?

[英]Why doesn't gcc zero the upper values of an XMM register when only using the lower value with SS/SD instructions?

例如用这样的 function,

int fb(char a, char b, char c, char d) {
    return (a + b) - (c + d);
}

gcc的总成 output 是,

fb:
        movsx   esi, sil
        movsx   edi, dil
        movsx   ecx, cl
        movsx   edx, dl
        add     edi, esi
        add     edx, ecx
        mov     eax, edi
        sub     eax, edx
        ret

含糊地,我知道movsx的目的是从寄存器的先前值中删除依赖关系,但老实说,我仍然不明白它试图删除什么样的依赖关系。 我的意思是,例如,是否存在movsx esi, sil ,如果某个值被写入esi ,那么使用esi的任何操作都必须等待,如果从esi读取值,任何修改值的操作esi将不得不等待,如果esi没有被任何操作使用,代码将继续运行。 movsx有什么不同? 我不能说编译器做错了,因为movsxmovzx (几乎?)在加载小于 32 位的值时总是由任何编译器生成。

除了我缺乏理解之外, gcc的行为与float不同。

float ff(float a, float b, float c, float d) {
    return (a + b) - (c + d);
}

编译为,

ff:
        addss   xmm0, xmm1
        addss   xmm2, xmm3
        subss   xmm0, xmm2
        ret

如果应用相同的逻辑,我相信 output 应该是这样的,

ff:
        movd    xmm0, xmm0
        movd    xmm1, xmm1
        movd    xmm2, xmm2
        movd    xmm3, xmm3
        addss   xmm0, xmm1
        addss   xmm2, xmm3
        subss   xmm0, xmm2
        ret

所以我实际上是在问两个问题。

  1. 为什么gcc的行为与float不同?
  2. movsx有什么不同?
  1. 返回值与 args 的宽度相同,因此不需要扩展。 在 x86 和 x86-64 调用约定中,允许类型宽度之外的寄存器部分保存垃圾。 (这适用于 GP integer 和向量寄存器。)

    除了 clang 依赖的未记录扩展外,调用者将窄参数扩展到 32 位; clang 将跳过您的char示例中的movsx指令。 https://godbolt.org/z/Gv5e4h3Eh

    向 x86-64 ABI 的指针添加 32 位偏移时是否需要符号或零扩展? 涵盖了高垃圾和调用约定的非官方扩展。

    由于您询问了错误依赖项,请注意编译器确实使用movaps xmm,xmm来复制标量。 (例如,在(ab) + (ad)中 GCC 错过的优化中,我们需要从a中减去两次。它是不可交换的,所以我们需要一个副本: https://godbolt.org/z/Tvx19raa3

  2. C integer 提升规则意味着窄输入的a+b等价于(int)a + (int)b 在所有 x86 / x86-64 ABI 中, char是有符号类型(例如,与 ARM 不同),因此需要将其符号扩展为int宽度,而不是零扩展。 并且绝对不会被截断。

    如果您通过返回char再次截断结果,编译器可以只做 8 位加法。 但实际上他们将使用 32 位添加并在那里留下任何高垃圾: https://godbolt.org/z/hGdbecPqv 这样做不是为了破坏/性能,只是为了正确。

    就性能而言,如果调用者编写了完整的寄存器(调用约定的非官方扩展无论如何都需要),或者在不单独重命名低 8 的 CPU 上,GCC 读取char的 32 位 reg 的行为很好来自 reg 的 rest (除 P6 系列之外的所有内容:SnB 系列仅重命名高 8 regs,除了原始 Sandybridge 本身。 为什么 GCC 不使用部分寄存器?


PS:没有像movd xmm0, xmm0这样的指令,只有一种不同形式的movq xmm0, xmm0 ,可以将 XMM 寄存器的低 64 位零扩展为完整的寄存器。

如果您想查看各种编译器尝试对低 dword 进行零扩展,无论是否使用 SSE4.1 insertps ,请查看 asm for __m128 foo(float f) { return _mm_set_ss(f); } __m128 foo(float f) { return _mm_set_ss(f); }在上面的 Godbolt 链接中。 例如,仅使用 SSE2,使用 pxor 将寄存器归零,然后movss xmm1, xmm0 否则, insertps或 xor-zero 和blendps

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM