问题 在GNU C inline asm中,对于单个操作数,xmm / ymm / zmm的修饰符是什么?


在试图回答时 具有内在和装配的嵌入式广播,我试图做这样的事情:

__m512 mul_broad(__m512 a, float b) {
    int scratch = 0;
    asm(
        "vbroadcastss  %k[scalar], %q[scalar]\n\t"  // want  vbr..  %xmm0, %zmm0
        "vmulps        %q[scalar], %[vec], %[vec]\n\t"

        // how it's done for integer registers
        "movw         symbol(%q[inttmp]), %w[inttmp]\n\t"  // movw symbol(%rax), %ax
        "movsbl        %h[inttmp], %k[inttmp]\n\t"  // movsx %ah, %eax
        : [vec] "+x" (a), [scalar] "+x" (b),  [inttmp] "=r" (scratch)
        :
        :
    );
    return a;
}

GNU C x86操作数修饰符 doc仅指定最多的修饰符 q (DI(DoubleInt)大小,64位)。运用 q 在向量寄存器上将始终将其归结为 xmm (从 ymm 要么 zmm)。

问题是:

向量寄存器大小之间有哪些修饰符可以改变?

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

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


背景/动机

我可以通过将标量作为输入操作数传递给我想要的结果,约束为与更宽的输出操作数在同一个寄存器中,但它更笨拙。 (这个用例的最大缺点是AFAIK必须使用操作数 - 而不是 [symbolic_name],因此在添加/删除输出约束时容易破损。)

// does what I want, by using a paired output and input constraint
__m512 mul_broad(__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链接


此外,我认为我试图解决的问题的整个方法将是一个死胡同,因为 多方替代约束 不要让你为不同的约束模式赋予不同的asm。我希望有 x 和 r 约束最终散发出来 vbroadcastss 从寄存器,而 m 约束最终会发出 vmulps (mem_src){1to16}, %zmm_src2, %zmm_dst (折叠的广播负载)。使用内联asm执行此操作的目的是gcc还不知道如何折叠 set1() 内存操作数转换为广播负载(但是clang确实如此)。

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


1625
2017-12-25 03:35


起源

在i386.c中查看ix86_print_operand,我会尝试%g。 - David Wohlferd
此外,在将输入与输出匹配时,您不必使用操作数编号: asm("" : [me] "=a" (a) : "[me]"(7));。 - David Wohlferd
@DavidWohlferd:谢谢!我很高兴知道这个 "[me]" 句法。这是匹配输出约束方法的主要反对意见。 - Peter Cordes
当Anger说GCC内联汇编的语法很复杂且难以学习时,他并不是在开玩笑。几天之后我觉得我或多或少得到了NASM,并且可以从文档中找出其他任何东西,但在某些情况下GCC inliene组装仍然令人困惑。我实际上并不介意AT&T语法,但GCC扩展语法很复杂。 - Z boson
@Zboson官方文档比以前更好。之前有一半被隐藏在GCC内部文件中。棘手的部分是你需要描述你的asm语句的每个效果和副作用,并且很容易忽略某些东西。 - Ross Ridge


答案:


从文件 GCC /配置/ I386 / i386.c 海湾合作委员会来源:

       b  - 打印指示操作数的寄存器的QImode名称。
        如果operands [0]为reg 0,则%b0将打印%al。
       w  - 同样,打印寄存器的HImode名称。
       k  - 同样,打印寄存器的SImode名称。
       q  - 同样,打印寄存器的DImode名称。
       x  - 同样,打印寄存器的V4SFmode名称。
       t  - 同样,打印寄存器的V8SFmode名称。
       g  - 同样,打印寄存器的V16SFmode名称。
       h  - 打印QImode名称为“高”寄存器,啊,bh,ch或dh。

同样来自 GCC /配置/ I386 / contraints.md

    ;;我们使用Y前缀来表示任意数量的条件寄存器集:
    ;; z第一个SSE寄存器。
    ;; i SSE2单元间移动到SSE寄存器使能
    ;; j SSE2内部单元从SSE寄存器启用
    ;; m MMX单元间移动到MMX寄存器启用
    ;; n MMX单元间从启用MMX寄存器移动
    ;;带有AND的零扩展被禁用时的整数寄存器
    ;; p禁用TARGET_PARTIAL_REG_STALL时的整数寄存器
    ;;当启用80387浮点运算时,f x87寄存器
    ;; r SSE regs在启用前缀避免时不需要REX前缀
    ;;并且所有SSE都注册了

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

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

请注意,这是从最新的SVN版本复制的。我不知道GCC的版本是什么,如果有的话,你添加了你感兴趣的特定修饰符和约束。


7
2017-12-25 05:52



效果很棒 关于Godbolt的gcc 5.3。除了虚假地生成堆栈帧和冗余的推/弹 %r10。看起来很像 gcc.gnu.org/bugzilla/show_bug.cgi?id=69041 (我昨天报道过),但它不仅影响64b目标 -m32。 - Peter Cordes


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

英特尔的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;
}

链接测试Godbolt: http://goo.gl/bOkjNu

(对不起,这不是你的问题的完整答案,但它似乎是有用的信息分享,并且评论太长了)


2
2018-01-12 04:22



(这里从其他地方随机抽出)这段代码实际上是错误的 - gcc基本上是把你所得到的东西打印出来的“东西”:vmovd%xmm0,%eax然而,你在xmm寄存器上有输出修饰符而不是整数寄存器。如果您交换那些,那么您将在64位获得正确的“rax”输出。您还需要ymm寄存器大小写的“y”约束。 - echristo
我暂时没想过这个,但我不认为你的纠正是正确的。 “q”修饰符在XMM寄存器上并不是错误:目标是找到一个语法,修改传递的YMM寄存器和ICC,Clang和GCC上相应XMM的输出程序集。 VMOVD需要一个32位整数寄存器(而不是VMOVQ): felixcloutier.com/x86/MOVD:MOVQ.html。但也许我没有正确地关注你。你可以链接到Godbolt的测试,准确显示你的建议吗? - Nathan Kurz
所以,你肯定是错的,老实说,你有很多方面。我应该更加小心。看起来你可能想要的是'x'修饰符: godbolt.org/g/mxRBVd  这将操作数视为V4SF类型并打印出正确的东西 - 至少在gcc中。它目前没有在clang中工作(提交一个bug,我会试着去了)并且也没有在编译器资源管理器具有的最新版本中工作:(否则你可能想尝试相应的内在函数?无论如何,对不起的困惑和希望这有点帮助。 - echristo