【问题标题】:Using a specific zmm register in inline asm在 inline asm 中使用特定的 zmm 寄存器
【发布时间】:2019-01-31 12:33:51
【问题描述】:

我可以告诉gcc-style inline assembly 将我的__m512i 变量放入一个特定 zmm 寄存器,例如zmm31

【问题讨论】:

  • Local Variable Registers 支持他们吗?
  • 您确定需要吗?显然,您要确保它不会将值放入您将在 asm 中使用的其他 zmm 寄存器之一,但大概这些寄存器会被破坏,而 gcc 已经不会将值放在那里。如果局部变量寄存器不起作用,我想你可以破坏所有的 zmm 寄存器 except zmm31。虽然输入+输出参数的数量有限制,但我不知道对 clobbers 的数量有任何限制(这并不意味着没有......)。
  • @MichaelPetch:我刚试过:godbolt.org/z/oMnFEAregister __m512i z31 asm("zmm31"); 与 clang 一起使用,但 gcc 似乎有一个错误,使其暂时使用 zmm31,但随后将输入放在不同的寄存器中以用于实际的 asm 语句。
  • @PeterCordes 您需要使用“v”约束。 “x”约束只允许前 16 个 SIMD 寄存器。
  • @RossRidge:谢谢,这就是我所缺少的。

标签: gcc assembly x86 inline-assembly


【解决方案1】:

与根本没有特定寄存器约束的目标(如 ARM)一样,使用 local register variables 获得广泛的约束,以便为 asm 语句选择特定寄存器。编译器仍然可以进行其他优化,因为唯一记录在案的 保证 本地寄存器效果是针对 asm 输入/输出。

即使没有asm,编译器也会首选指定的寄存器。 (因此,您可以使用register int ebx asm("ebx"); return ebx; 之类的东西编写看似有效但通常不安全的代码。GCC 文档是保证行为/面向未来的原因,即使当前的 gcc 更喜欢使用指定的寄存器足以浪费约束与指定寄存器不兼容时的说明,见下文。)

无论如何,这种使用 register-asm local 变量是唯一保证它们可以工作的东西

#include <immintrin.h>
__m512i foo() {
    register __m512i z31 asm("zmm31") = _mm512_set1_epi32(123);
    register __m512i z30 asm("zmm30");

    asm("vmovdqa64 %1, %0  # from inline asm"
        : "=v"(z30)
        : "v"(z31)
       );
    return z30;
}

the Godbolt compiler explorer,用clang6.0编译成这个:

    # clang -O3 -march=skylake-avx512
    vbroadcastss    .LCPI0_0(%rip), %zmm31 # zmm31 = [1.72359711E-43,1.72359711E-43,1.72359711E-43,1.72359711E-43,1.72359711E-43,1.72359711E-43,1.72359711E-43,1.72359711E-43,1.72359711E-43,1.72359711E-43,1.72359711E-43,1.72359711E-43,1.72359711E-43,1.72359711E-43,1.72359711E-43,1.72359711E-43]
    vmovdqa64       %zmm31, %zmm30        # from inline asm
    vmovaps %zmm30, %zmm0
    retq

和 gcc8.2:

# gcc -O3 -march=skylake-avx512
foo():
    movl    $123, %eax
    vpbroadcastd    %eax, %zmm31
    vmovdqa64 %zmm31, %zmm30  # from inline asm
    vmovdqa64       %zmm30, %zmm0
    ret

注意 "v" 约束,它允许任何 EVEX 向量寄存器 (0..31),而 "x" 只允许前 16 个。"x" 被记录为“任何 SSE 寄存器",但也适用于 AVX YMM 寄存器。 https://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html.

为此使用"x" 并没有导致任何警告,但是使用 gcc "x" 赢得了与寄存器变量声明相比,因此它选择了 %zmm2 和 %zmm1 (奇怪的是不是 zmm0 所以额外的举动是必需的)。因此, register-asm 声明确实降低了我们的效率。

使用 clang 它仍然使用 zmm31 和 zmm30,显然违反了"x" 约束,因此如果您在寄存器操作数的 XMM 或 YMM 部分使用没有 EVEX 版本的指令,它将无法汇编,例如AVX2 vpcmpeqd ymm,ymm,ymm(比较向量,不比较掩码)。 (In GNU C inline asm, what're the modifiers for xmm/ymm/zmm for a single operand?)。

//#ifndef __clang__
__m512i broken_with_clang() {
    register __m512i z31 asm("zmm31") = _mm512_set1_epi32(123);
    register __m512i z30 asm("zmm30") = _mm512_setzero_si512();
    // notice that gcc still inits these in zmm31 and 30, *then* copies
    // so register asm costs us efficiency.

    // AVX512 only has compares into k registers, not into YMM registers.
    asm("vpcmpeqd %t1, %t0, %t0  # from inline asm. input was %0"
        : "+x"(z30)
        : "x"(z31)
       );
    return z30;
}
//#endif

使用 clang 我们会得到每个操作数的错误;我猜clang不支持t修饰符来获取寄存器的YMM名称(因为即使我完全删除了register ... asm()的东西,clang6.0也会失败。)

<source>:21:9: error: invalid operand in inline asm: 'vpcmpeqd ${1:t}, ${0:t}, ${0:t}  # from inline asm. input was $0'
    asm("vpcmpeqd %t1, %t0, %t0  # from inline asm. input was %0"
        ^
...
<source>:21:9: error: unknown token in expression
<inline asm>:1:11: note: instantiated into assembly here
        vpcmpeqd , ,   # from inline asm. input was %zmm30

但是 gcc 编译它就好了:

broken_with_clang():
    movl    $123, %eax
    vpbroadcastd    %eax, %zmm31
    vpxord  %xmm30, %xmm30, %xmm30

    vmovdqa64       %zmm30, %zmm1    # extra overhead because of register asm
    vmovdqa64       %zmm31, %zmm2    # which didn't match the constraints

    vpcmpeqd %ymm2, %ymm1, %ymm1  # from inline asm. input was %zmm1

    vmovdqa64       %zmm1, %zmm0     # extra overhead because gcc didn't pick zmm0
    ret

【讨论】:

  • 对于将“此功能唯一支持的用途是在调用扩展 asm 时为输入和输出操作数指定寄存器”添加到“新”asm 文档时有些犹豫。我在这里的措辞是故意挑衅的(仅?!?),目的是排除任何其他受支持的用法,以便我可以记录它们或提供它们作为示例。最终承认没有任何问题,并且一整类潜在的错误被明确而明确地描述为不受支持。不会阻止人们尝试,但是当它不起作用时,有一个(可链接的)线索来说明原因。
猜你喜欢
  • 2012-05-13
  • 1970-01-01
  • 1970-01-01
  • 2023-03-11
  • 2018-09-24
  • 1970-01-01
  • 2015-11-09
  • 2016-04-03
相关资源
最近更新 更多