【问题标题】:operand type mismatch for `vpbroadcastd''vpbroadcastd' 的操作数类型不匹配
【发布时间】:2015-12-19 19:06:14
【问题描述】:

我试图找到 Xeon Phi 平台的 KNC 广播指令。但我找不到任何指示。相反,我尝试在程序集中使用这个 AVX _mm512_set1_epi32 内在函数。我有两个问题:首先有没有KNC广播指令?其次,当我编译下面的代码时,我得到了“vpbroadcastd”错误的操作数类型不匹配。

int op = 2;
__asm__("vmovdqa32 %0,%%zmm0\n\t"
            "mov %1, %%eax\n\t"
            "vpbroadcastd %%eax, %%zmm1\n\t"
            "vpsravd %%zmm1,%%zmm0,%%zmm1\n\t"
            "vmovdqa32 %%zmm1,%0;"
            : "=m" (tt[0]): "m" (op));

其中 tt 使用以下代码定义,我使用 k1om-mpss-linux-gcc 编译器来编译此代码

int * tt = (int *) aligned_malloc(16 * sizeof(int),64);

【问题讨论】:

  • 根据 Xeon Phi 指令集手册 VPBROADCASTD 仅将内存位置作为源操作数。 AVX 2 版本采用内存位置或 XMM 寄存器。两者都不允许 EAX 作为源。
  • @RossRidge 感谢您的回复。我的问题是在 Xeon Phi 指令中使用广播或设置指令的正确方法是什么?
  • @RossRidge: 常规 AVX512F 确实 允许从 GP 寄存器进行广播。 Xeon Phi没有那个吗?这将解释问题。在这种情况下,解决方案就是先不加载到 eax 中,因为 OP 会强制编译器将其放入内存中。
  • @RossRidge:Peter 建议使用 _mm512_set1_epi32(int) 内在函数。我只想知道这条指令的汇编版本。不知道r32是什么?以及如何加载到 r32?
  • 我想也许你在这里咬得比你能咀嚼的还多。正如英特尔的文档所述,没有对应于_mm512_set1_epi32 的汇编指令。理想情况下,此内在函数不会生成指令,广播是使用 {1to16} 操作数转换免费完成的。要有效地使用 Xeon Phi 进行组装,您需要了解类似的内容。你不应该问r32的含义之类的基本问题。

标签: assembly broadcast xeon-phi


【解决方案1】:

我查看了 AVX2 如何使用内部函数执行此操作,并注意到广播从内存中读取,就像使用 KNC 一样。查看来自 AVX2 内在函数的程序集,我编写了内联程序集,它做同样的事情。

#include <stdio.h>
#include <x86intrin.h>
void foo(int *A, int n) {
    __m256i a16 = _mm256_loadu_si256((__m256i*)A);
    __m256i t = _mm256_set1_epi32(n);
    __m256i s16 = _mm256_srav_epi32(a16,t);
    _mm256_storeu_si256((__m256i*)A, s16);
}

void foo2(int *A, int n) {
    __asm__("vmovdqu      (%0),%%ymm0\n"
            "vpbroadcastd (%1), %%ymm1\n"
            "vpsravd      %%ymm1, %%ymm0, %%ymm0\n"
            "vmovdqu      %%ymm0, (%0)"
            :
            : "r" (A), "r" (&n)
            : "memory"
        );
}

int main(void) {
    int x[8];
    for(int i=0; i<8; i++) x[i] = 1<<i;
    for(int i=0; i<8; i++) printf("%8d ", x[i]); puts("");
    foo2(x,2);
    for(int i=0; i<8; i++) printf("%8d ", x[i]); puts("");
}

这是我对 KNC 的猜测(使用对齐负载):

void foo2_KNC(int *A, int n) {
    __asm__("vmovdqa32      (%0),%%zmm0\n"
            "vpbroadcastd   (%1), %%zmm1\n"
            "vpsravd        %%zmm1, %%zmm0, %%zmm0\n"
            "vmovdqa32      %%zmm0, (%0)"
            :
            : "r" (A), "r" (&n)
            : "memory"
        );
}

使用 KNC 和 AVX512 似乎有一种更有效的方法。

Intel says 关于“2.5.3 广播”部分中的 AVX12:

EVEX 编码提供了一个位域来对某些加载操作指令的数据广播进行编码

然后给出例子

vmulps zmm1, zmm2, [rax] {1to16}

在哪里

{1to16} 原语加载一个 float32(单精度)元素 从记忆中提取,复制 16 次,形成一个 16 个 32 位浮点元素的向量,乘以 16 个 float32 元素,对应的元素在 第一个源操作数向量,并将 16 个结果中的每一个放入目标操作数中。

我以前从未使用过他的语法,但你可以尝试

void foo2_KNC(int *A, int n) {
__asm__("vmovdqa32      (%0),%%zmm0\n\t"
        "vpsravd        (%1)%{1to16}, %%zmm0, %%zmm0\n\t"
        "vmovdqa32      %%zmm0, (%0)\t"
        :
        : "r" (A), "r" (&n)
        : "memory", "%zmm0"
    );

}

这会产生

vmovdqa32      (%rax),%zmm0
vpsravd        (%rdx){1to16}, %zmm0, %zmm0
vmovdqa32      %zmm0, (%rax)

Agner Fog 顺便在the documentation for objconv 中有一个标题为“8.4 AVX-512 和 Knights Corner 指令的汇编语法”的部分,他说

这两个指令集非常相似,但具有不同的可选指令属性。这两个指令集中的指令在前缀中只有一位不同,即使对于其他方面相同的指令也是如此。

根据他的文档,NASM 支持 AVX-512 和 KNC 语法,因此您可以在 NASM 中尝试这种语法。

【讨论】:

  • 投反对票的人(或理解我为什么投反对票的人)你能解释一下你的投反对票吗?
  • 刚刚注意到了一些事情,现在 asm 的噪音更小了:广播然后可变移位????为什么不vmovd %1, %%zmm1(带有rm 约束)然后vpsrad %%zmm1, %%zmm0, %%zmm0?我假设 KNC 的 512b 向量将该 SSE2 指令扩展到 512,从 %zmm1 的低 64b 中获取所有元素的移位计数。
  • @PeterCordes 我认为基于 Ross Ridge 的评论我被否决了“理想情况下,这个内在函数不会生成指令,广播是使用 {1to16} 操作数转换免费完成的。为了有效利用与 Xeon Phi 组装你需要知道这样的事情。”
  • @PeterCordes 我正在尝试让 GCC 和 AVX512 来做这个 {1to16} 的事情,但到目前为止我所看到的只是vpbroadcastd %esi, %zmm0{%k1}{z}。我不得不承认我是新手。
  • 如果找不到参考,我可能会尝试在 YASM 或 NASM 中组装广播负载,然后反汇编以获得 AT&T 语法。
【解决方案2】:

这个答案的早期版本是错误的。根据An Intels PDF of the KNC insn from Sep 2012, which I hope is current/up-to-date,512b vpsrad 仅在立即计数时可用。当您在 GP 寄存器(而不是内存)中进行计数时,这确实显得相当不方便。

似乎可变计数移位 (vpsravd) 是在 KNC 上进行非立即计数移位的唯一方法,即使每个元素的计数都相同。因为它可以使用广播负载来计算班次,所以这不是一个大问题。 KNC 似乎也有来自寄存器源 (zmm1 {aaaa}) 的“混合”随机播放或广播,但我不确定该广播的宽度是多少。

这不会在普通编译器上编译:{1to16} 被忽略,并且您会收到错误消息“对于 `vpsravd' 这种类型的操作数需要广播”。 IDK 如果这只是一个语法问题,使用 intel-syntax 而不是 AT&T。

// compile with -masm=intel
// todo: something clever to use vpsrad when the shift count is a compile-time constant
void shift_KNC(int *A, int n) {

  __asm__ volatile(
    // ".intel_syntax noprefix\n"
    "vmovdqa32      zmm0, %0\n\t"
    "vpsravd        zmm0, zmm0, %1 {1to16}\n\t"
    "vmovdqa32      %0,  zmm0\n\t"
    : "+m" (*(__m512i*)A)
    : "m" (n) /* force it to memory */
    : "%zmm0"
  );
}

仍然使用完整的“内存”clobber,因为我们只是告诉编译器使用第一个整数作为输入/输出内存操作数,而不是下一个 16。

如果您可以将 zmm 值保留在内存中,而不是在内联 asm 的微小片段之间存储/重新加载,那么性能会好得多


根据Xeon Phi Knights Corner intrinsics with GCC,gcc 不支持 KNC 的内部函数。


我认为我拥有的 PDF 是针对 AVX512 (KNL/Skylake-E) 的。关于 KNC 的 IDK;它可能没有这个。 (特别是:英特尔® 架构 指令集扩展编程参考,从 2014 年 10 月开始。)

VPBROADCASTD 的GP 寄存器源格式,只需要AVX512F。 VPBROADCASTD zmm1 {k1}{z}, r32。内在是

__m512i _mm512_maskz_set1_epi32( __mmask16 k, int a);

没有一个没有面具,但也许可以试试_mm512_set1_epi32(int)

顺便说一句,你的内联程序集compiles ok with a normal compiler on godbolt。 (“二进制”复选框使它实际组装然后反汇编,所以我确信指令被接受了。)

如果您仍然使用内联 asm,而不是内在函数,请确保整理您的代码:如果您需要编译器将 op 放入内存中,请使用广播加载,而不是 @ 987654336@ 进入 GP 寄存器并从那里广播。更好的是,为vpsravd 使用广播加载内存操作数:VPSRAVD zmm1 {k1}{z}, zmm2, zmm3/m512/m32bcst。那么你根本不需要 VPBROADCAST 指令。 (我假设编译器会使用内在函数来做到这一点。)

【讨论】:

  • @Zboson:你确定吗?一年前的stackoverflow.com/questions/26933394/… 听起来 gcc 可能永远不会支持 KNC。此外,与 AVX2 不同,KNC 的 512b vpsrad 似乎只需要立即数。因此,如果 OP 需要非立即计数,他尝试广播是正确的。
  • 似乎不能指望 AVX512 内在函数,即使它们与我一样与 KNC 相同。这适用于某些操作,例如加载和存储以及 mulps,例如在此 answer 中,但显然在 vpsrad 情况下存在断开连接。
  • 我想我明白了"vpsravd (%1)%{1to16}, %%zmm0, %%zmm0\n\t" 产生vpsravd (%rdx){1to16}, %zmm0, %zmm0
  • @PeterCordes,是的,它组装起来了。我刚试过二进制,没问题。它甚至显示操作码。如果我正确理解 Agner,KNC 和 AVX512 之间的唯一区别,至少在指令相同时是一个位。
  • 我正要问一个关于内存广播(使用 1to16)的问题,因为我无法让 GCC 用内在函数来做这件事,然后我发现 this 非常有趣。我怀疑如果 OPs 案例中的值 (op) 已经在寄存器中,那么显式广播指令会更好。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-08
  • 2023-03-27
  • 2018-11-02
  • 2015-07-23
  • 1970-01-01
相关资源
最近更新 更多