我正在将索引的地址输入到表中以进行扩展的内联汇编操作,但是即使不需要使用lea
或{{ 1}}。 GCC正在使用相对RIP地址。
我正在创建一个函数,用于将两个连续的位转换成两部分的XMM掩码(每个位1个四字掩码)。为此,我将-Ofast -fomit-frame-pointer
(内部-Os -f...
)与8位字节表中的内存操作数结合使用,并将这些位作为索引。
当我使用内部函数时,GCC会生成与使用扩展内联汇编完全相同的代码。
我可以直接将内存操作嵌入到ASM模板中,但这将始终强制使用RIP相对寻址(而且我不喜欢强迫自己进入解决方法)。
_mm_cvtepi8_epi64
带有vpmovsxbq
(带有typedef uint64_t xmm2q __attribute__ ((vector_size (16)));
// Used for converting 2 consecutive bits (as index) into a 2-elem XMM mask (pmovsxbq)
static const uint16_t MASK_TABLE[4] = { 0x0000, 0x0080, 0x8000, 0x8080 };
xmm2q mask2b(uint64_t mask) {
assert(mask < 4);
#ifdef USE_ASM
xmm2q result;
asm("vpmovsxbq %1, %0" : "=x" (result) : "m" (MASK_TABLE[mask]));
return result;
#else
// bad cast (UB?), but input should be `uint16_t*` anyways
return (xmm2q) _mm_cvtepi8_epi64(*((__m128i*) &MASK_TABLE[mask]));
#endif
}
和不带有)的输出程序集:
-S
我所期望的(我已经删除了所有多余的东西):
USE_ASM
答案 0 :(得分:1)
唯一的RIP相对寻址模式是RIP + rel32
。 RIP + reg不可用。
(在机器代码中,32位代码过去有2种冗余方式来编码[disp32]
。x86-64使用较短(无SIB)形式作为RIP相对,较长SIB形式作为{{1} }。
如果您使用[sign_extended_disp32]
为Linux进行编译,则GCC将能够使用32位绝对地址访问静态数据,因此它可以使用-fno-pie -no-pie
之类的模式。对于MacOS,这是不可能的,因为其基址始终大于2 ^ 32。 x86-64 MacOS完全不支持32位绝对寻址。
在PIE可执行文件(或通常像库这样的PIC代码)中,您需要设置相对RIP的LEA来为静态数组建立索引。或者其他任何情况下静态地址不能容纳32位和/或不是链接时间常数。
是的,由于缺少内在函数的指针源版本,内在函数很难从狭窄的源表示__ZL10MASK_TABLE(,%rdi,2)
负载。
pmovzx/sx
是不安全的:如果禁用优化,则很可能会加载*((__m128i*) &MASK_TABLE[mask]
16字节,但地址将未对齐。仅当编译器将负载折叠到movdqa
的内存操作数中才安全,该操作数具有2字节的内存操作数,因此不需要对齐。
事实上,当前的GCC 确实会在reg-reg pmovzxbq
之前以movdqa
之类的movdqa xmm0, XMMWORD PTR [rax+rdi*2]
16字节负载来编译代码。这显然是错过的优化。 :( clang / LLVM(MacOS安装为pmovzx
的确会将负载折叠到gcc
中。
安全的方法是使用pmovzx
之类的东西,然后希望编译器将零扩展从2字节优化为4字节,并在启用优化时将_mm_cvtepi8_epi64( _mm_cvtsi32_si128(MASK_TABLE[mask]) )
折叠成一个负载。或者,即使您确实想要16,也可以尝试movd
进行32位加载。但是,上次我尝试使用编译器将{64}固有的64位加载折叠到_mm_loadu_si32
的内存操作数中。 GCC和clang仍然失败,但是ICC19成功。 https://godbolt.org/z/IdgoKV
我以前曾写过这本书:
您对pmovzxbw
的选择似乎很奇怪。您不需要符号扩展名,因此我会选择pmovsx
(pmovzx
)。但是,实际上在任何CPU上效率都不高。
这里的查找表只需要少量的静态数据即可。如果您的面罩范围更大,您可能想研究一下 is there an inverse instruction to the movemask instruction in intel avx2?获取广播+ AND +(移位或比较)等替代策略。
如果您经常这样做,最好使用整个高速缓存行,其中包含4个16字节向量常量,因此您不需要_mm_cvt_epu8_epi64
指令,只需索引到pmovzx
的对齐表中即可或xmm2
向量,它们可以是任何其他SSE指令的存储源。使用__m128i
来获取同一缓存行中的所有常量。
如果您要针对具有BMI2的英特尔CPU,也可以考虑alignas(64)
+ pdep
+ movd xmm0, eax
reg-reg的本质。 (不过,pmovzxbq
在AMD上运行缓慢)。