带内联汇编的GCC和-Ofast为内存操作数生成额外的代码

时间:2019-07-11 08:45:34

标签: c++ gcc x86-64 compiler-optimization inline-assembly

我正在将索引的地址输入到表中以进行扩展的内联汇编操作,但是即使不需要使用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

1 个答案:

答案 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的选择似乎很奇怪。您不需要符号扩展名,因此我会选择pmovsxpmovzx)。但是,实际上在任何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上运行缓慢)。