在Clang下解决缺少Yz机器约束的问题?

jww*_*jww 5 c++ sse sha clang inline-assembly

如果__SHA__未定义,我们使用内联汇编使SHA指令可用.在GCC下我们使用:

GCC_INLINE __m128i GCC_INLINE_ATTRIB
MM_SHA256RNDS2_EPU32(__m128i a, const __m128i b, const __m128i c)
{
    asm ("sha256rnds2 %2, %1, %0" : "+x"(a) : "xm"(b), "Yz" (c));
    return a;
}
Run Code Online (Sandbox Code Playgroud)

Clang不遵守 GCC的Yz约束条款(参见Clang 3.2 Issue 13199和Clang 3.9 Issue 32727),该sha256rnds2指令要求:

Yz

    First SSE register (%xmm0).
Run Code Online (Sandbox Code Playgroud)

我们mov为Clang 添加了一个:

asm ("mov %2, %%xmm0; sha256rnds2 %%xmm0, %1, %0" : "+x"(a) : "xm"(b), "x" (c) : "xmm0");
Run Code Online (Sandbox Code Playgroud)

性能每字节约3个周期.在我的2.2 GHz Celeron J3455测试机(带有SHA扩展的Goldmont)上,大约为230 MiB/s.它不平凡.

看看反汇编,k当执行两轮时,Clang没有围绕SHA 进行优化:

Breakpoint 2, SHA256_SSE_SHA_HashBlocks (state=0xaaa3a0,
    data=0xaaa340, length=0x40) at sha.cpp:1101
1101        STATE1 = _mm_loadu_si128((__m128i*) &state[4]);
(gdb) disass
Dump of assembler code for function SHA256_SSE_SHA_HashBlocks(unsigned int*, unsigned int const*, unsigned long):
   0x000000000068cdd0 <+0>:     sub    $0x308,%rsp
   0x000000000068cdd7 <+7>:     movdqu (%rdi),%xmm0
   0x000000000068cddb <+11>:    movdqu 0x10(%rdi),%xmm1
   ...
   0x000000000068ce49 <+121>:   movq   %xmm2,%xmm0
   0x000000000068ce4d <+125>:   sha256rnds2 %xmm0,0x2f0(%rsp),%xmm1
   0x000000000068ce56 <+134>:   pshufd $0xe,%xmm2,%xmm3
   0x000000000068ce5b <+139>:   movdqa %xmm13,%xmm2
   0x000000000068ce60 <+144>:   movaps %xmm1,0x2e0(%rsp)
   0x000000000068ce68 <+152>:   movq   %xmm3,%xmm0
   0x000000000068ce6c <+156>:   sha256rnds2 %xmm0,0x2e0(%rsp),%xmm2
   0x000000000068ce75 <+165>:   movdqu 0x10(%rsi),%xmm3
   0x000000000068ce7a <+170>:   pshufb %xmm8,%xmm3
   0x000000000068ce80 <+176>:   movaps %xmm2,0x2d0(%rsp)
   0x000000000068ce88 <+184>:   movdqa %xmm3,%xmm4
   0x000000000068ce8c <+188>:   paddd  0x6729c(%rip),%xmm4        # 0x6f4130
   0x000000000068ce94 <+196>:   movq   %xmm4,%xmm0
   0x000000000068ce98 <+200>:   sha256rnds2 %xmm0,0x2d0(%rsp),%xmm1
   ...
Run Code Online (Sandbox Code Playgroud)

例如,0068ce8c虽然0068ce98应该是:

paddd  0x6729c(%rip),%xmm0        # 0x6f4130
sha256rnds2 %xmm0,0x2d0(%rsp),%xmm1
Run Code Online (Sandbox Code Playgroud)

我猜我们选择的内联asm指令有点过时了.

我们如何解决YzClang下缺乏机器约束的问题?什么模式避免了优化代码中的中间移动?


试图使用显式寄存器变量:

const __m128i k asm("xmm0") = c;
asm ("sha256rnds2 %2, %1, %0" : "+x"(a) : "xm"(b), "x" (k));
return a;
Run Code Online (Sandbox Code Playgroud)

结果是:

In file included from sha.cpp:24:
./cpu.h:831:22: warning: ignored asm label 'xmm0' on automatic variable
        const __m128i k asm("xmm0") = c;
                            ^
./cpu.h:833:7: error: invalid operand for instruction
        asm ("sha256rnds2 %2, %1, %0" : "+x"(a) : "xm"(b), "x" (k));
             ^
<inline asm>:1:21: note: instantiated into assembly here
        sha256rnds2 %xmm1, 752(%rsp), %xmm0
                           ^~~~~~~~~~
In file included from sha.cpp:24:
./cpu.h:833:7: error: invalid operand for instruction
        asm ("sha256rnds2 %2, %1, %0" : "+x"(a) : "xm"(b), "x" (k));
             ^
<inline asm>:1:21: note: instantiated into assembly here
        sha256rnds2 %xmm3, 736(%rsp), %xmm1
                           ^~~~~~~~~~
...
Run Code Online (Sandbox Code Playgroud)

Mic*_*tch 3

我根据标签创建了这个答案,inline assembly没有提到任何特定语言。扩展程序集模板已经假定使用语言扩展。

如果Yz约束不可用,您可以尝试创建一个临时变量来告诉CLANG使用哪个寄存器而不是约束。您可以通过所谓的显式寄存器变量来做到这一点:

您可以定义一个局部寄存器变量并将其与指定的寄存器关联,如下所示:

 register int *foo asm ("r12");
Run Code Online (Sandbox Code Playgroud)

这里 r12 是应该使用的寄存器的名称。请注意,这与定义全局寄存器变量的语法相同,但对于局部变量,声明出现在函数内。register 关键字是必需的,并且不能与 static 组合。寄存器名称必须是目标平台的有效寄存器名称。

在您的情况下,您希望强制使用xmm0寄存器。您可以c使用显式寄存器将参数分配给临时变量,并将该临时变量用作扩展内联程序集的参数。这是GCC/CLANG中显式寄存器的主要目的。

GCC_INLINE __m128i GCC_INLINE_ATTRIB
MM_SHA256RNDS2_EPU32(__m128i a, const __m128i b, const __m128i c)
{
   register const __m128i tmpc asm("xmm0") = c;
   __asm__("sha256rnds2 %2, %1, %0" : "+x"(a) : "x"(b), "x" (tmpc));
    return a;
}
Run Code Online (Sandbox Code Playgroud)

编译器现在应该能够提供一些优化,因为它对如何xmm0使用寄存器有更多的了解。

当您放入mov %2, %%xmm0;模板CLANG(和GCC)时,不要对指令进行任何优化。基本装配和扩展装配模板是一个黑盒子,它只知道如何根据约束进行基本替换。


这是使用上面方法的反汇编。它是用clang++和编译的-std=c++03。额外的动作不再存在:

 register int *foo asm ("r12");
Run Code Online (Sandbox Code Playgroud)