适用于AVX512掩码寄存器(k1 ... k7)的GNU C内联asm输入约束?

时间:2019-05-02 05:23:25

标签: c gcc assembly inline-assembly avx512

AVX512为其算术命令引入了opmask功能。一个简单的示例:godbolt.org

#include <immintrin.h>
__m512i add(__m512i a, __m512i b) {
    __m512i sum;
    asm(
        "mov ebx, 0xAAAAAAAA;                                   \n\t"
        "kmovw k1, ebx;                                         \n\t"
        "vpaddd %[SUM] %{k1%}%{z%}, %[A], %[B];  # conditional add   "
        :   [SUM]   "=v"(sum)
        :   [A]     "v" (a),
            [B]     "v" (b)
        : "ebx", "k1"  // clobbers
       );
    return sum;
}

-march=skylake-avx512 -masm=intel -O3

 mov ebx,0xaaaaaaaa
 kmovw k1,ebx
 vpaddd zmm0{k1}{z},zmm0,zmm1

问题是必须指定k1。

是否存在像整数"r"这样的输入约束,除了它选择k寄存器而不是通用寄存器之外?

2 个答案:

答案 0 :(得分:6)

虽然它没有记录,但看起来here是:

  

(define_register_constraint“ Yk”“ TARGET_AVX512F?MASK_REGS:   NO_REGS“” @internal可用作谓词的任何掩码寄存器,   即k1-k7。“)

对此进行编辑:

asm(
"vpaddd %[SUM] %{%[k]}, %[A], %[B]" 
: [SUM] "=v"(sum) 
: [A] "v" (a), [B] "v" (b), [k] "Yk" (0xaaaaaaaa) );

似乎产生正确的输出。

也就是说,我通常尝试discourage个人使用内联汇编(和未记录的功能)。您可以使用_mm512_mask_add_epi32吗?

答案 1 :(得分:6)

__mmask16实际上是unsigned short的typedef(以及其他用于普通整数类型的掩码类型),因此我们只需要一个约束即可将其传递到k寄存器中。


我们必须去挖掘gcc来源config/i386/constraints.md来找到它:

任何掩码寄存器的约束为"k"或将"Yk"用于k1..k7(可以用作谓词,与k0不同)。您可以将"=k"操作数用作例如,将其作为掩码的目标。

很明显,您可以将"=Yk"(tmp)__mmask16 tmp结合使用,以使编译器为您分配寄存器,而不仅仅是在决定使用的"k"寄存器上声明clobbers。


首先,如果可以避免的话, https://gcc.gnu.org/wiki/DontUseInlineAsm 理解 asm很好,但是可以使用它来读取编译器输出和/或找出最佳选择,然后编写可以编译所需方式的内在函数。诸如https://agner.org/optimize/https://uops.info/之类的性能调优信息通过asm助记符列出事物,它们比内在函数更短/更容易记住,但是您可以通过助记符搜索在https://software.intel.com/sites/landingpage/IntrinsicsGuide/上找到内在函数

内部函数还将使编译器将负载折叠到内存源操作数中以用于其他指令;使用AVX512,甚至可以广播负载!内联汇编程序强制编译器使用单独的加载指令。 即使是"vm"输入,也不会让编译器选择广播负载作为内存源,因为它不知道您正在使用的指令的广播元素宽度

使用_mm512_mask_add_epi32_mm512_maskz_add_epi32 ,尤其是如果您已经在使用__m512i中的<immintrin.h>类型。


此外,您的asm有一个错误:您正在使用{k1}合并掩码而不是{k1}{z}零掩码,但是您将未初始化的__m512i sum;与一个仅输出的"=v"约束作为合并目标!作为独立功能,它恰巧合并到a中,因为调用约定的格式为ZMM0 =第一个输入=返回值寄存器。但是当内联到其他函数中时,您绝对不能假定sum将选择与a相同的寄存器。最好的选择是对"+v"(a)使用读/写操作数,并将其用作目标和第一个源。

合并屏蔽仅对"+v"读/写操作数有意义。(或在具有多个指令的asm语句中,您已经编写了一次输出,并且想要将另一个结果合并到其中。)

内在性会阻止您犯此错误;合并掩码版本为合并目标提供了额外的输入。 (asm目标操作数)。


使用“ Yk”的示例

// works with -march=skylake-avx512 or -march=knl
// or just -mavx512f but don't do that.
#include <immintrin.h>
__m512i add_zmask(__m512i a, __m512i b) {
    __m512i sum;
    asm(
        "vpaddd %[SUM] %{%[mask]%}%{z%}, %[A], %[B];  # conditional add   "
        :   [SUM]   "=v"(sum)
        :   [A]     "v" (a),
            [B]     "v" (b),
         // no clobbers needed, unlike your question which I fixed with an edit
       );
    return sum;
}

使用cc早于4.9编译,但实际上不这样做,因为它不了解-march=skylake-avx512,甚至没有针对Skylake或KNL进行调整的设置。

Godbolt compiler explorer

# gcc8.3 -O3 -march=skylake-avx512 or -march=knl
add(long long __vector, long long __vector):
        mov     eax, -21846
        kmovw   k1, eax         # compiler-generated
       # inline asm starts
        vpaddd zmm0 {k1}{z}, zmm0, zmm1;  # conditional add   
       # inline asm ends
        ret
要使-mavx512bw-march=skylake-avx512上工作,需要

knl(由"Yk"表示,但不是int。如果您使用-march=knl进行编译,则整数文字需要转换为__mmask16__mask8,因为unsigned int = __mask32无法用于掩码。

[mask] "Yk" (0xAAAA)要求AVX512BW,即使该常数确实适合16位,也只是因为裸整数常量始终具有类型int。 ({vpaddd zmm每个向量有16个元素,因此我将常数缩短为16位。)使用AVX512BW,您可以传递更宽的常数,也可以忽略更小的常数。

  • gcc6和更高版本支持-march=skylake-avx512。使用它来设置调整并启用所有功能。优选gcc8或至少gcc7。如果您在内联汇编之外使用新的ISA扩展(例如AVX512),则较新的编译器会生成不太笨拙的代码。
  • gcc5支持-mavx512f -mavx512bw,但不了解Skylake。
  • gcc4.9不支持-mavx512bw

遗憾的是,

"Yk"尚未记录在https://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html中。

由于Ross在In GNU C inline asm, what're the modifiers for xmm/ymm/zmm for a single operand?上的回答,我知道在GCC来源中可以找到什么地方