首先 movss,然后用零 unpcklps,不改变高零。为什么?

The*_*enk 1 x86 assembly sse x86-64

我是 x86 的新手,没有任何经验,所以这段代码对我来说看起来有点过时了。这样做有什么目的吗?

说明是:

rcx+000003F8 = 32 位浮点数

xmm0 = 0(全部 128 位)

movss xmm4,[rcx+000003F8]
unpcklps xmm4,xmm0
Run Code Online (Sandbox Code Playgroud)

“unpcklps xmm4,xmm0”会不会过时,因为它不会改变 xmm4 中的任何内容?

Pet*_*des 5

这可能是来自 MSVC 19.20 或更早版本的脑死亡代码生成,它不知道_mm_setr_ps(x, 0,0,0)_mm_set_ss(x).

#include <immintrin.h>

__m128 foo(float *xptr){
    return _mm_set_ss(*xptr);  // load and zero-extend a float into a vector
}

__m128 bar(float *xptr){
    return _mm_setr_ps(*xptr, 0,0,0);  // same, but the compiler has to notice
             // that the explicit zeros can be produced for free by MOVSS
}
Run Code Online (Sandbox Code Playgroud)

GCC 和 Clang 以及 MSVC 19.21 及更高版本均按预期编译movss xmm0, DWORD PTR [rcx](或[rdi]针对 x86-64 System V 调用约定)。

但正如我们在 Godbolt 上看到的,MSVC 19.20.27525 及更早版本制作了这个脑死亡汇编,在另一个寄存器中生成高位元素并将它们洗牌。

foo     PROC                                          ; COMDAT
        movss   xmm0, DWORD PTR [rcx]
        ret     0
foo     ENDP


bar     PROC                                          ; COMDAT
        movss   xmm0, DWORD PTR [rcx]
        xorps   xmm1, xmm1
        unpcklps xmm0, xmm1
        xorps   xmm2, xmm2
        unpcklps xmm0, xmm2
        ret     0
bar     ENDP
Run Code Online (Sandbox Code Playgroud)

它确实知道它可以生成零而xorps不是加载常量,但甚至没有注意到它可以重复使用相同的归零向量两次。它确实设法“仅”使用两条指令,而不是我们在仅使用 SSE2 (而不是 SSE4.1 )unpcklps的一般情况下所需的三个指令。_mm_setr_ps(a,b,c,d)insertps

即使是其他编译器的Godbolt 上最古老的版本,GCC 4.1 和 Clang 3.0,也优化_mm_set_ps为仅加载。MSVC 19.14 来自 Visual Studio 2017;我认为 MSVC 的内在函数代码生成现在大部分都很好,尽管它不会对内在函数进行太多优化(如果有的话),但最近情况更糟。


某些执行路径可能跳转到此块,而不运行将 XMM0 归零的代码。

也许您可以设置一个条件断点,unpcklps该断点只会在非零时触发xmm0。如果在运行程序时它从未跳闸,并且您没有看到包含它的基本块的任何分支,那么它可能只是来自 MSVC 的愚蠢代码生成。