【问题标题】:How to get bits of specific xmm registers?如何获取特定 xmm 寄存器的位?
【发布时间】:2020-11-25 08:19:31
【问题描述】:

所以我想获取特定 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__ 有效吗?

【问题讨论】:

    标签: c gcc x86 sse inline-assembly


    【解决方案1】:

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

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

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

    对于将在何处订购此产品并没有太多保证。其他代码(asm volatile 可能会有所帮助,或者对于更强的排序也可以使用"memory"clobber)。也不能保证 GCC 不会首先将寄存器用于其他用途。 (特别是像任何 xmm reg 一样的调用破坏寄存器。)但它至少在我测试的版本中确实可以工作。

    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 参数),那可能可行。这当然是 ISO C 中的未定义行为,但如果你真的停止内联,效果取决于调用约定。只要您确保它是noinline,那么链接时优化就不会进行跨文件内联。

    当然,插入函数调用这一事实会改变调用者中的寄存器分配,因此这仅对您将要调用的函数有所帮助。

    【讨论】:

    【解决方案2】:

    如何获取和访问存储在__m128i 中的 64 位整数位?

    您必须将__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];
    

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

    或者我上面的__asm__ 有用吗?

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

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

    【讨论】:

    • 在 64 位目标上,unsigned __int128 与 XMM 一样宽,因此来自 "=x"(m128i) 输出的 type-pun 或 memcpy 或 _mm_store_si128 可以工作。
    • 更重要的是, register-asm 本地变量是一种读取函数传入 reg 的不稳定方式,并且只能保证使 asm 语句的 "r""x" 操作数选择一个特定的登记;旧行为通常有效,但现在未记录 = 不受支持 (gcc.gnu.org/onlinedocs/gcc/Local-Register-Variables.html)。您的回答似乎建议省略 asm 语句。这可能会因clang而中断。
    • @PeterCordes __int128 是一个 gcc 特定的扩展,它不是普遍可用的。 Re asm 声明,我故意没有提供任何关于如何捕获寄存器值的指导,因为这不是问题所在。首先,这并不明显。包含 "=r" 输出约束的 asm 语句确实没有意义,应该修复或删除。
    • 不是 GCC 特定的,特定于 C 的 GNU 方言。至少受 GCC、clang 和 ICC 支持。就像 OP 已经在使用的 register __m128i my_val __asm__("xmm0"); 一样。就我而言,困难的部分(使这个问题完全值得回答,而不是作为print a __m128i variable 的副本关闭)是捕获XMM 寄存器的值。正如我在回答中指出的那样,您不能安全地删除 asm() 语句。
    • @PeterCordes 就捕获而言,我无论如何都不会使用该语法,因为您无法控制编译器围绕 asm 语句生成的代码。寄存器可能被编译器破坏,代码将打印垃圾。您可以做的最好的事情是使用内联汇编来保存寄存器,从而最大限度地减少丢失它们的值的可能性。但同样,这并不明显,因为作者可能只想打印他在代码中拥有的 __m128i 值。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-14
    • 1970-01-01
    • 2017-10-29
    • 1970-01-01
    • 2012-01-30
    相关资源
    最近更新 更多