如何获取特定 xmm 寄存器的位?

How to get bits of specific xmm registers?

所以我想获取特定xmm 寄存器的值或状态。这主要用于崩溃日志或只是为了查看寄存器的状态以进行调试。我试过了,但它似乎不起作用:

#include <x86intrin.h>
#include <stdio.h>

int main(void) {

     register __m128i my_val __asm__("xmm0");
     __asm__ ("" :"=r"(my_val));
     printf("%llu %llu\n", my_val & 0xFFFFFFFFFFFFFFFF, my_val << 63);
  return 0;
}

据我所知,store 相关内在函数不会将 __m128i 视为 POD 数据类型,而是作为对其中一个 xmm 寄存器的引用。

如何获取和访问存储在 __m128i 中的 64 位整数位?或者我的 __asm__ 以上是否有效?

How do I get and access the bits stored in the __m128i as 64 bit integers?

您必须将 __m128i 向量转换为一对 uint64_t 变量。您可以使用转换内在函数来做到这一点:

uint64_t lo = _mm_cvtsi128_si64(my_val);
uint64_t hi = _mm_cvtsi128_si64(_mm_unpackhi_epi64(my_val, my_val));

...或者通过内存:

uint64_t buf[2];
_mm_storeu_si128((__m128i*)buf, my_val);
uint64_t lo = buf[0];
uint64_t hi = buf[1];

后者在性能方面可能会更差,但如果您打算仅将其用于调试,也可以。如果需要,适应不同大小的元素也很简单。

Or does my __asm__ above work?

不,不是。 “=r”输出约束不允许向量寄存器,例如作为输出传递的 xmm0,它只允许通用寄存器。没有通用寄存器是 128 位宽的,因此 asm 语句没有意义。

此外,我应该注意到 my_val << 63 以错误的方式移动了值。如果您想输出假设的 128 位值的高半部分,那么您应该向右移动,而不是向左移动。除此之外,向量上的移位要么未实现,要么 act on each element of the vector 而不是整个向量,具体取决于编译器。但这部分没有实际意义,因为对于上面的代码,您不需要任何移位来输出两半。

如果你真的想知道 register 值,而不是 __m128i C 变量值,我建议使用像 GDB 这样的调试器。 print /x $xmm0.v2_int64 在断点处停止时。

尝试在函数顶部捕获寄存器是一件非常不稳定且不可靠的事情(感觉你已经走错了设计路径)1。但是您使用 register-asm 本地变量走在正确的轨道上。但是,xmm0 无法匹配 "=r" 约束,只能匹配 "=x"。有关使用空 asm 模板告诉编译器您希望 C 变量成为寄存器中的内容的更多信息,请参阅 Reading a register value into a C variable

不过,您确实需要 asm volatile("" : "=x"(var)); 语句; GNU C register-asm 局部变量没有任何保证,除非用作 asm 语句的操作数。 (无论如何,GCC 通常会将您的 var 保存在该寄存器中,但 IIRC clang 不会。)

对于在哪里订购 wrt 并没有太多保证。其他代码(asm volatile 可能会有所帮助,或者为了更强的排序也使用 "memory" 破坏)。也不能保证 GCC 不会首先将寄存器用于其他用途。 (特别是 call-clobbered 注册器,就像任何 xmm 注册器一样。)但它至少碰巧在我测试的版本中工作。

print a __m128i variable 展示了如何将 __m128i 打印为两个 64 位的一半,或者打印为其他元素大小。编译器通常会优化 _mm_store_si128 / 重新加载到随机播放中,无论如何这是为了打印所以保持简单。

在 x86-64 上的 GNU C 中也可以选择使用 unsigned __int128 tmp;


#include <immintrin.h>
#include <stdint.h>
#include <stdio.h>
#ifndef __cplusplus
#include <stdalign.h>
#endif

// If you need this, you're probably doing something wrong.
// There's no guarantee about what a compiler will have in XMM0 at any point
void foo() {
    register __m128i xmm0 __asm__("xmm0");
    __asm__ volatile ("" :"=x"(xmm0));

    alignas(16) uint64_t buf[2];
    _mm_store_si128((__m128i*)buf, xmm0);
    printf("%llu %llu\n", buf[1], buf[0]);   // I'd normally use hex, like %#llx
}

这会先打印高半部分(最重要的部分),因此从左到右读取两个元素,我们得到 buf.

中内存地址降序排列的每个字节

它用 GCC 和 clang (Godbolt) 编译成我们想要的 asm,在读取它之前没有踩到 xmm0。

# GCC10.2 -O3
foo:
        movhlps xmm1, xmm0
        movq    rdx, xmm0                 # low half -> RDX
        mov     edi, OFFSET FLAT:.LC0
        xor     eax, eax
        movq    rsi, xmm1                 # high half -> RSI
        jmp     printf

脚注 1:

如果您确定您的函数没有内联,您可以利用调用约定来获取 xmm0..7(对于 x86-64 System V)或 xmm0..3 的传入值,如果您没有整数参数 (Windows x64).

__attribute__((noinline))
void foo(__m128i xmm0, __m128i xmm1, __m128i xmm2, etc.) {
  // do whatever you want with the xmm0..7 args
}

如果您想为调用者使用的函数提供不同的原型(省略 __m128i args),这也许可行。这当然是 ISO C 中的未定义行为,但如果你真的停止内联,效果取决于调用约定。只要您确保它是 noinline,那么 link-time 优化就不会进行 cross-file 内联。

当然,仅仅插入一个函数调用就会改变调用者的寄存器分配,所以这只会对你要调用的函数有所帮助。