使用 (float&)int 进行类型双关可以正常工作,(float const&)int 会像 (float)int 一样转换吗?

2024-05-10

VS2019,发布,x86。

template <int i> float get() const {
    int f = _mm_extract_ps(fmm, i);
    return (float const&)f;
}

使用时return (float&)f;编译器使用

extractps m32, ...
movss xmm0, m32

.正确的结果

使用时return (float const&)f;编译器使用

extractps eax, ...
movd xmm0, eax

.错误的结果

T& 和 T const& 的主要思想是先 T 再 const。 Const只是程序员的某种约定。你知道你可以绕过它。但是汇编代码中没有任何 const,但类型 float 是。我认为对于 float& 和 float const& 来说,它必须是汇编中的浮点表示(CPU 寄存器)。我们可以使用中间int reg32,但最终解释必须是float。

而此时看起来就像是回归,因为这之前工作得很好。在这种情况下使用 float& 绝对是奇怪的,因为我们不应该考虑 float const& 安全性,但 float& 的临时变量确实值得怀疑。

微软回答:

您好,真相发现者,感谢您提供独立的重现。当它发生的时候, 这种行为实际上是正确的。正如我的同事@Xiang Fan [MSFT] 在内部电子邮件中描述:

[a c-style cast] 执行的转换尝试以下操作 顺序: (4.1) — const_cast (7.6.1.11), (4.2) — static_cast (7.6.1.9), (4.3) — 一个 static_cast 后跟一个 const_cast, (4.4) — 一个reinterpret_cast (7.6.1.10),或者 (4.5) — 一个reinterpret_cast,后跟一个const_cast,

如果可以用列出的多种方式解释转换 上面,使用列表中第一个出现的解释。

因此,在您的情况下, (const float &) 转换为 static_cast,其效果是“初始化表达式是 隐式转换为“cv1 T1”类型的纯右值。临时的 应用物化转换并且引用绑定到 结果。”

但在另一种情况下, (float &) 转换为 reinterpret_cast 因为 static_cast 无效, 与reinterpret_cast(&operand) 相同。

您观察到的实际“错误”是一个强制转换:“转换 将 float 类型值“1.0”转换为等效的 int 类型值“1”, 而另一个演员说“找到 1.0 的位表示作为 float,然后将这些位解释为 int”。

因此,我们建议不要使用 c 风格的强制转换。

Thanks!

MS论坛链接:https://developercommunity.visualstudio.com/content/problem/411552/extract-ps-intrinsics-bug.html https://developercommunity.visualstudio.com/content/problem/411552/extract-ps-intrinsics-bug.html

有任何想法吗?

附:我真正想要什么:

float val = _mm_extract_ps(xmm, 3);

在手动组装中我可以写:extractps val, xmm0, 3其中 val 是 float 32 内存变量。只有一个!操作说明。我希望在编译器生成的汇编代码中看到相同的结果。没有洗牌或任何其他过多的指示。最糟糕的可接受的情况是:extractps reg32, xmm0, 3; mov val, reg32.

我关于 T& 和 T const& 的观点: 两种情况下变量的类型必须相同。但现在float&将把 m32 解释为 float32 并且float const&将把 m32 解释为 int32。

int main() {
    int z = 1;
    float x = (float&)z;
    float y = (float const&)z;
    printf("%f %f %i", x, y, x==y);
    return 0;
}

输出:0.000000 1.000000 0

真的可以吗?

此致, 真相探寻者


有一个关于 C++ 强制转换语义的有趣问题(微软已经为您简要回答了),但它与您滥用_mm_extract_ps导致首先需要一个类型双关语。(并且仅显示等效的 asm,省略 int->float 转换。)如果其他人想在另一个答案中扩展标准语言,那就太好了。

TL:DR:用这个代替:它是零或一 shufps。没有摘录,没有类型双关。

template <int i> float get(__m128 input) {
    __m128 tmp = input;
    if (i)     // constexpr i means this branch is compile-time-only
        tmp = _mm_shuffle_ps(tmp,tmp,i);  // shuffle it to the bottom.
    return _mm_cvtss_f32(tmp);
}

如果您确实有一个内存目标用例,您应该在 asm 中查找一个需要float*输出 arg,不是需要结果的函数xmm0。 (是的,这是一个用例extractps指令,但可以说不是_mm_extract_ps内在函数。 gcc 和 clang 使用extractps优化时*out = get<2>(in),尽管 MSVC 错过了这一点并且仍然使用 shufps + movss。)


您显示的两个 asm 块只是将 xmm0 的低 32 位复制到某处,而没有转换为 int。你遗漏了重要的不同之处,只显示了无用复制的部分float位模式从 xmm0 出来然后返回,以两种不同的方式(到寄存器或到内存)。movd是未修改的位的纯副本,就像 movss 负载一样。

在你强制使用它之后,编译器会选择使用它extractps根本不。通过寄存器并返回的延迟比存储/重新加载要低,但 ALU 微指令更多。

The (float const&)尝试输入双关语确实包括从 FP 到整数的转换,您没有显示这一点。好像我们需要更多的理由来避免类型双关的指针/引用转换,这确实意味着不同的东西:(float const&)f 采用整数位模式(来自_mm_extract_ps) as an int并将其转换为float.

我把你的代码在 Godbolt 编译器资源管理器上 https://godbolt.org/#z:OYLghAFBqd5TKALEBjA9gEwKYFFMCWALugE4A0BIEAZugHZEDKqAhgDbYgCMAdAEwAOAJzCALAFYADIIDsohaPIArHuXat6oVAFJ%2BAIT37yAZ3QBXUqi4ByPQGYC9VO3M4A1DvsBhAgFs/JyJSJ14kL1wdKQBBImw/AAcNOM8fIPcCCPcadnRWIndgbCIIAH1Sv24hDPoE8yIASk9ZQxj3dvdyyuqiRNSAERq6oi9W6I6MmncIAiaJ%2BYXFpfcAehWMr36pdz9sTRN3IiRsd2xOXcYMg45SPcwATxr3JNZrKPHF3oSBzoDSkyQ5hoOWwpQSJggX3IUNmo3aa1O7BMJwBQJBGQKJEOx3cACN0EQSH5eO8JrciJZ6L8/KVUAA3IgmEylGj2fiQxINUbvHSyfrvBEAWSYADVvO4ALQAcTp0zp2FQJCsHHYTQSrCZ2GupGATwAHgEpAL1gB1YhIZ4aJzuTDmPy49x6gBsYmyGqIbHY7HI7lYOqu7gY2JRRFeAGsfZpMBj3AAqTDoLWx9zKcwmAqCgDyIqYTHcuVY0YA7uaLAVUcD2E5gO8cnkCkUiOyulVBDUmryxmTipTCsUvN5%2BBEZvQufYxrz%2BTFawWG8VuKUS0dStg9cFXkRwbSGOmyhVW9kAh2WqSOukpptqSu16QN2CITQAj7uGOux1yb3aLP3Bh6Om9E6DQ0Ny058jyMR1vkfZEAuS5INe66Klu9AML%2Bu4ttUj5%2BMeb7tOePwVDSq6IUQ960E%2B7gviBHztB%2BpBUl%2B9aePwgHAeOPJgTENgNOoIA2BINjkPQfFSIJ6B8d4RhGO4ZiWNYzH2NwglECJ3E8WGIDSLxNhiIJfggGI9i8LIEj8BIghiCI/BmfwwjcEJqnkOJNiCSYIBSNCqk8XAsAoBgiQEJwFBUBA/kJIF2CkGg7ClPQ2BFqULrkDQgVxKQbkQLijm4k4fr3HxSnkP5FxEJm9DsPlNiieQOB%2BJowCcI5hC3IqBDym5VWCauCr1LYhVBGcjlVrit6kPc3hYH1ykhPpNhKTxGhaGgUkGOoBC4m5kA8egCREAQO58RKmb8JKMquRYVhcC%2B2kCQ5nVOXxzqun4Jh0qg7h0twwi8NwroQCaACSAByuBNBA%2BDEGQCn2e4E0BUFCn2E0kkGEYyleTxxyFpF1ALXxunkPpZm8C6ToWbIYiyNw9iGVI9j2Hd1XOa57meZ13mIBAfnoPDkWUNQYURVFLixfFiViMlqWRRlWX3Tl9B5QVgnFdgjBlRVTXxPVjX3c1Cp7e1jndagvVK5QjCDfdw2jeNk1m8E/hKwt9XLajq3DZtEDbbt%2B1/odx3nXJV14/xgnCfdzlPTsr3vZ932/dMgMg2DENKtDPpw%2BFCMOGIyMrfo6PsyHBP6fYUgk1IUgSE6shOvYLpSGIVcSIzYl8SzHkqUX5AaVp7B8fYYeOczbOiRz8A%2BcgaA81nfMhYLQUgMAgj8JL7BpTL2W5WNZsq2r5WVdVtXa7YR8EC1Btakbeo9XEZsDf3VvrTbE04PbM1O%2BoLu6G7xge/A3s9oHRsEdewkoTQqnAcRW8koXrIgJrJS6PAQ63XDkzR6ggnQShdIUVA71BACGmKnKGDgYaZyFtDPOv9C5j0xncHGXse6aQ8o/EuIB6a8Hplw7hPCnStwei5UwrMu5j3IJPLmIAyzDH5qFGeQs1DYEIEqNQRZbwJE/o/VBw8%2BKkPcHBR0mDsGumAHg9wBD%2BA0LUuQLGOAoqMNYXpEA1lOFSC%2BmIJ0DcpB10MhLNBbdBFuU7l5MRnNua82CgLORi8XD1VkJXNeG9qCy2qvLRWc1lY8xKurQ%2Bglj5aB1mfC%2BbUr73WNqbdJ5s4iP2qtbPKr8prQg/uk52S0f6GHdutT2gDfYdVAeAyBEoTTQNYLAzUCCLrWGQTdIeEcMFYJwTErQ7hZC8HLtscGSiSH8EUhnKJkUFL8Coe0guo8rEOMJgZMQnCXSyH4JXFiTo6bbP4SPQJpyGjqWYdpQeLz27vJDhY35giRFWPlOlX2BkgA%3D%3D看看你遗漏了什么。

float get1_with_extractps_const(__m128 fmm) {
    int f = _mm_extract_ps(fmm, 1);
    return (float const&)f;
}

;; from MSVC -O2 -Gv  (vectorcall passes __m128 in xmm0)
float get1_with_extractps_const(__m128) PROC   ; get1_with_extractps_const, COMDAT
    extractps eax, xmm0, 1   ; copy the bit-pattern to eax

    movd    xmm0, eax      ; these 2 insns are an alternative to pxor xmm0,xmm0 + cvtsi2ss xmm0,eax to avoid false deps and zero the upper elements
    cvtdq2ps xmm0, xmm0    ; packed conversion is 1 uop
    ret     0

GCC 是这样编译的:

get1_with_extractps_const(float __vector(4)):    # gcc8.2 -O3 -msse4
        extractps       eax, xmm0, 1
        pxor    xmm0, xmm0            ; cvtsi2ss has an output dependency so gcc always does this
        cvtsi2ss        xmm0, eax     ; MSVC's way is probably better for float.
        ret

显然,MSVC 确实为类型双关定义了指针/引用转换的行为。普通 ISO C++ 不会(严格别名 UB),其他编译器也不会。使用memcpy类型双关语,或联合(GNU C 和 MSVC 在 C++ 中支持作为扩展)。当然在this在这种情况下,将你想要的向量元素类型双关为整数然后返回是可怕的。

仅适用于(float &)fgcc 是否警告严格别名违规。GCC / clang 同意 MSVC 的观点,即只有这个版本是类型双关语,而不是具体化float来自隐式转换。C++ 很奇怪!

float get1_with_extractps_nonconst(__m128 fmm) {
    int f = _mm_extract_ps(fmm, 1);
    return (float &)f;
}

<source>: In function 'float get_with_extractps_nonconst(__m128)':
<source>:21:21: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
     return (float &)f;
                     ^

gcc 优化掉了extractps共。

# gcc8.2 -O3 -msse4
get1_with_extractps_nonconst(float __vector(4)):
    shufps  xmm0, xmm0, 85    ; 0x55 = broadcast element 1 to all elements
    ret

Clang 使用 SSE3movshdup将元素 1 复制到 0。(并将元素 3 复制到 2)。 但 MSVC 没有,这是永远不要使用它的另一个原因:

float get1_with_extractps_nonconst(__m128) PROC
    extractps DWORD PTR f$[rsp], xmm0, 1     ; store
    movss   xmm0, DWORD PTR f$[rsp]          ; reload
    ret     0

不要使用_mm_extract_ps为了这

你的两个版本都很糟糕,因为这不是_mm_extract_ps or extractps are for. 英特尔 SSE:为什么“_mm_extract_ps”返回“int”而不是“float”? https://stackoverflow.com/questions/5526658/intel-sse-why-does-mm-extract-ps-return-int-instead-of-float

A float寄存器中的元素与向量的低位元素相同。高元素不需要归零。如果他们这样做了,你会想使用insertps它可以根据立即数做xmm,xmm和零元素。

Use _mm_shuffle_ps将你想要的元素带到寄存器的低位,然后is标量浮点数。 (你可以告诉 C++ 编译器_mm_cvtss_f32)。这应该编译为shufps xmm0,xmm0,2, without an extractps or any mov.

template <int i> float get() const {
    __m128 tmp = fmm;
    if (i)                               // i=0 means the element is already in place
        tmp = _mm_shuffle_ps(tmp,tmp,i);  // else shuffle it to the bottom.
    return _mm_cvtss_f32(tmp);
}

(我跳过使用_MM_SHUFFLE(0,0,0,i)因为那等于i.)

If your fmm在内存中,而不是寄存器中,那么希望编译器能够优化掉洗牌,然后movss xmm0, [mem]。 MSVC 19.14 确实设法做到了这一点,至少对于堆栈情况下的 function-arg 来说是这样。我没有测试其他编译器,但 clang 可能应该设法优化掉_mm_shuffle_ps;它非常擅长看穿洗牌。

测试用例证明可以有效编译

例如具有函数的非类成员版本的测试用例,以及将其内联到特定的调用者i:

#include <immintrin.h>

template <int i> float get(__m128 input) {
    __m128 tmp = input;
    if (i)                  // i=0 means the element is already in place
        tmp = _mm_shuffle_ps(tmp,tmp,i);  // else shuffle it to the bottom.
    return _mm_cvtss_f32(tmp);
}

// MSVC -Gv (vectorcall) passes arg in xmm0
// With plain dumb x64 fastcall, arg is on the stack, and it *does* just MOVSS load without shuffling
float get2(__m128 in) {
    return get<2>(in);
}

来自 Godbolt 编译器资源管理器 https://godbolt.org/#z:OYLghAFBqd5TKALEBjA9gEwKYFFMCWALugE4A0BIEAZugHZEDKqAhgDbYgCMAdAEwAOAJzCALAFYADIIDsohaPIArHuXat6oVAFJ%2BAIT37yAZ3QBXUqi4ByPQGYC9VO3M4A1DvsBhAgFs/JyJSJ14kL1wdKQBBImw/AAcNOM8fIPcCCPcadnRWIndgbCIIAH1Sv24hDPoE8yIASk9ZQxj3dvdyyuqiRNSAERq6oi9W6I6MmncIAiaJ%2BYXFpfcAehWMr36pdz9sTRN3IiRsd2xOXcYMg45SPcwATxr3JNZrKPHF3oSBzoDSkyQ5hoOWwpQSJggX3IUNmo3aa1O7BMJwBQJBGQKJEOx3cACN0EQSH5eO8JrciJZ6L8/KVUAA3IgmEylGj2fiQxINUbvHSyfrvBEAWSYADVvO4ALQAcTp0zp2FQJCsHHYTQSrCZ2GupGATwAHgEpAL1gB1YhIZ4aJzuTDmPy49x6gBsYmyGqIbHY7HI7lYOqu7gY2JRRFeAGsfZpMBj3AAqTDoLWx9zKcwmAqCgDyIqYTHcuVY0YA7uaLAVUcD2E5gO8cnkCkUiOyulVBDUmryxmTipTCsUvN5%2BBEZvQufYxrz%2BTFawWG8VuKUS0dStg9cFXkRwbSGOmyhVW9kAh2WqSOukpptqSu16QN2CITQAj7uGOux1yb3aLP3Bh6Om9E6DQ0Ny058jyMR1vkfZEAuS5INe66Klu9AML%2Bu4ttUj5%2BMeb7tOePwVDSq6IUQ960E%2B7gviBHztB%2BpBUl%2B9aePwgHAeOPJgTENgNOoIA2BINjkPQfFSIJ6B8d4RhGO4ZiWNYzH2NwglECJ3E8WGIDSLxNhiIJfggGI9i8LIEj8BIghiCI/BmfwwjcEJqnkOJNiCSYIBSNCqk8XAsAoBgiQEJwFBUBA/kJIF2CkGg7ClPQ2BFqULrkDQgVxKQbkQLijm4k4fr3HxSnkP5FxEJm9DsPlNiieQOB%2BJowCcI5hC3IqBDym5VWCauCr1LYhVBGcjlVrit6kPc3hYH1ykhPpNhKTxGhaGgUkGOoBC4m5kA8egCREAQO58RKmb8JKMquRYVhcC%2B2kCQ5nVOXxzqun4Jh0qg7h0twwi8NwroQCaACSAByuBNBA%2BDEGQCn2e4E0BUFCn2E0kkGEYyleTxxyFpF1ALXxunkPpZm8C6ToWbIYiyNw9iGVI9j2Hd1XOa57meZ13mIBAfnoPDkWUNQYURVFLixfFiViMlqWRRlWX3Tl9B5QVgnFdgjBlRVTXxPVjX3c1Cp7e1jndagvVK5QjCDfdw2jeNk1m8E/hKwt9XLajq3DZtEDbbt%2B1/odx3nXJV14/xgnCfdzlPTsr3vZ932/dMgMg2DENKtDPpw%2BFCMOGIyMrfo6PsyHBP6fYUgk1IUgSE6shOvYLpSGIVcSIzYl8SzHkqUX5AaVp7B8fYYeOczbOiRz8A%2BcgaA81nfMhYLQUgMAgj8JL7BpTL2W5WNZsq2r5WVdVtXa7YR8EC1Btakbeo9XEZsDf3VvrTbE04PbM1O%2BoLu6G7xge/A3s9oHRsEdewkoTQqnAcRW8koXrIgJrJS6PAQ63XDkzR6ggnQShdIUVA71BACGmKnKGDgYaZyFtDPOv9C5j0xncHGXse6aQ8o/EuIB6a8Hplw7hPCnStwei5UwrMu5j3IJPLmIAyzDH5qFGeQs1DYEIEqNQRZbwJE/o/VBw8%2BKkPcHBR0mDsGumAHg9wBD%2BA0LUuQLGOAoqMNYXpEA1lOFSC%2BmIJ0DcpB10MhLNBbdBFuU7l5MRnNua82CgLORi8XD1VkJXNeG9qCy2qvLRWc1lY8xKurQ%2Bglj5aB1mfC%2BbUr73WNqbdJ5s4iP2qtbPKr8prQg/uk52S0f6GHdutT2gDfYdVAeAyBEoTTQNYLAzUCCLrWGQTdIeEcMFYJwTErQ7hZC8HLtscGSiSH8EUhnKJkUFL8Coe0guo8rEOMJgZMQnCXSyH4JXFiTo6bbP4SPQJpyGjqWYdpQeLz27vJDhY35giRFWPlOlX2BkgA%3D%3D、MSVC、clang 和 gcc 的 asm 输出:

;; MSVC -O2 -Gv
float get<2>(__m128) PROC               ; get<2>, COMDAT
        shufps  xmm0, xmm0, 2
        ret     0
float get<2>(__m128) ENDP               ; get<2>

;; MSVC -O2  (without Gv, so the vector comes from memory)
input$ = 8
float get<2>(__m128) PROC               ; get<2>, COMDAT
        movss   xmm0, DWORD PTR [rcx+8]
        ret     0
float get<2>(__m128) ENDP               ; get<2>
# gcc8.2 -O3 for x86-64 System V (arg in xmm0)
get2(float __vector(4)):
        shufps  xmm0, xmm0, 2   # with -msse4, we get unpckhps
        ret
# clang7.0 -O3 for x86-64 System V (arg in xmm0)
get2(float __vector(4)):
        unpckhpd        xmm0, xmm0      # xmm0 = xmm0[1,1]
        ret

clang 的随机优化器简化为unpckhpd,在某些旧 CPU 上速度更快。不幸的是它没有注意到它可以使用movhlps xmm0,xmm0,速度也很快,并且短了 1 个字节。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 (float&)int 进行类型双关可以正常工作,(float const&)int 会像 (float)int 一样转换吗? 的相关文章

随机推荐