如何获取特定 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 内联。
当然,仅仅插入一个函数调用就会改变调用者的寄存器分配,所以这只会对你要调用的函数有所帮助。
所以我想获取特定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 内联。
当然,仅仅插入一个函数调用就会改变调用者的寄存器分配,所以这只会对你要调用的函数有所帮助。