在 GNU C 内联汇编中,xmm/ymm/zmm 的单个操作数的大小覆盖修饰符是什么?

In GNU C inline asm, what are the size-override modifiers for xmm/ymm/zmm for a single operand?

在尝试回答 时,我试图做这样的事情:

__m512 mul_bcast(__m512 a, float b) {
    asm(
        "vbroadcastss  %k[scalar], %q[scalar]\n\t"  // want  vbcast..  %xmm0, %zmm0
        "vmulps        %q[scalar], %[vec], %[vec]\n\t"
        : [vec] "+x" (a), [scalar] "+&x" (b)
        : :
    );
    return a;
}

GNU C x86 Operand Modifiers 文档仅指定最大为 q 的修饰符(DI (DoubleInt) 大小,64 位)。在向量寄存器上使用 q 将始终将其降低为 xmm(从 ymmzmm)。例如标量寄存器:

 long scratch = 0;  // not useful instructions, just syntax demo
 asm(
     "movw         symbol(%q[inttmp]), %w[inttmp]\n\t"  // movw symbol(%rax), %ax
     "movsbl        %h[inttmp], %k[inttmp]\n\t"     // movsx %ah, %eax
   :  [inttmp] "+r" (scratch)
   :: "memory"  // we read some index in symbol[]
 );

问题:

在向量寄存器大小之间更改的修饰符是什么?

此外,输入或输出操作数是否有任何特定的大小限制?不同于通用 x 的东西,它最终可能是 xmm、ymm 或 zmm,具体取决于您放在括号中的表达式的类型。

题外话:
clang 似乎有一些 Yi / Yt 约束(不是修饰符),但我也找不到相关文档。 clang 甚至不会编译它,即使向量指令被注释掉,因为它不喜欢 +x 作为 __m512 向量的约束。


背景/动机

我可以通过将标量作为输入操作数传递来获得我想要的结果,将其限制在与更宽的输出操作数相同的寄存器中,但它比较笨拙。 (这个用例的最大缺点是 AFAIK 匹配约束只能通过操作数编号引用,而不是 [symbolic_name],因此当 adding/removing 输出约束时它很容易被破坏。)

// does what I want, by using a paired output and input constraint
__m512 mul_bcast(__m512 a, float b) {
    __m512 tmpvec;
    asm(
        "vbroadcastss  %[scalar], %[tmpvec]\n\t"
        "vmulps        %[tmpvec], %[vec], %[vec]\n\t"
        : [vec] "+x" (a), [tmpvec] "=&x" (tmpvec)
        : [scalar] "1" (b)
        :
    );

  return a;
}

Godbolt compiler explorer


此外,我认为我试图解决的问题的整个方法将是死胡同,因为 Multi-Alternative constraints 不允许您为不同的约束模式提供不同的 asm。我希望 xr 约束最终从寄存器发出 vbroadcastss,而 m 约束最终发出 vmulps (mem_src){1to16}, %zmm_src2, %zmm_dst(折叠广播-加载)。使用内联 asm 这样做的目的是 gcc 还不知道如何将 set1() 内存操作数折叠成广播负载(但 clang 知道)。

无论如何,这个具体问题是关于向量寄存器的操作数修饰符和约束。请关注这一点,但欢迎对另一个问题发表评论和回答。 (或者更好,只是评论/回答 Z Boson 关于嵌入式广播的问题。)

来自 GCC 源的文件 gcc/config/i386/i386.c

       b -- print the QImode name of the register for the indicated operand.
        %b0 would print %al if operands[0] is reg 0.
       w --  likewise, print the HImode name of the register.
       k --  likewise, print the SImode name of the register.
       q --  likewise, print the DImode name of the register.
       x --  likewise, print the V4SFmode name of the register.
       t --  likewise, print the V8SFmode name of the register.
       g --  likewise, print the V16SFmode name of the register.
       h -- print the QImode name for a "high" register, either ah, bh, ch or dh.

gcc/config/i386/contraints.md类似:

    ;; We use the Y prefix to denote any number of conditional register sets:
    ;;  z   First SSE register.
    ;;  i   SSE2 inter-unit moves to SSE register enabled
    ;;  j   SSE2 inter-unit moves from SSE register enabled
    ;;  m   MMX inter-unit moves to MMX register enabled
    ;;  n   MMX inter-unit moves from MMX register enabled
    ;;  a   Integer register when zero extensions with AND are disabled
    ;;  p   Integer register when TARGET_PARTIAL_REG_STALL is disabled
    ;;  f   x87 register when 80387 floating point arithmetic is enabled
    ;;  r   SSE regs not requiring REX prefix when prefixes avoidance is enabled
    ;;  and all SSE regs otherwise

此文件还定义了一个 "Yk" 约束,但我不知道它在 asm 语句中的效果如何:

    (define_register_constraint "Yk" "TARGET_AVX512F ? MASK_EVEX_REGS : NO_REGS"
    "@internal Any mask register that can be used as predicate, i.e. k1-k7.")

请注意,这都是从最新的 SVN 修订版复制而来的。我不知道 GCC 的哪个版本(如果有)添加了您感兴趣的特定修饰符和约束。

似乎所有最新版本的 GCC 都接受 'q' 和 'x' 作为修饰符来打印 YMM 寄存器的 XMM 版本。

Intel 的 icc 看起来接受 'q',但不接受 'x'(至少通过版本 13.0.1)。

[编辑:好吧,它在下面的这个小例子中有效,但在一个真实的测试用例中,我遇到了 icc 14.0.3 接受 'q' 但写 'ymm' 的问题.]

[编辑:使用更新版本的 icc 进行测试,我发现 icc 15 和 icc 16 都不能与 'q' 或 'x' 一起使用。]

但 Clang 3.6 及更早版本不接受这两种语法。至少在 Godbolt 上,Clang 3.7 会同时崩溃!

// inline assembly modifiers to convert ymm to xmm

#include <x86intrin.h>
#include <stdint.h>

// gcc also accepts "%q1" as "%x1" 
// icc accepts "%q1" but not "%x1"
// clang-3.6 accepts neither
// clang-3.7 crashes with both!

#define ASM_MOVD(vec, reg)       \
__asm volatile("vmovd %q1, %0" : \
               "=r" (reg) :      \
               "x" (vec)         \
    );          

uint32_t movd_ymm(__m256i ymm) {
   uint32_t low;
   ASM_MOVD(ymm, low);
   return low;
}

uint32_t movd_xmm(__m128i xmm) {
   uint32_t low;
   ASM_MOVD(xmm, low);
   return low;
}

Link 在 Godbolt 上测试:http://goo.gl/bOkjNu

(很抱歉,这不是您问题的完整答案,但这似乎是值得分享的有用信息,但评论时间太长)