title: Why does gcc add extra vmovdqa64 instructions with _mm512_dpbusds_epi32?
body: <p><code>g++</code> is generating extra move instructions and I'm not sure why.  <a href="https://godbolt.org/z/47vELM" rel="nofollow noreferrer">Godbolt link</a></p>
  
  <p>This is happening around the <a href="https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm512_dpbusds_epi32&amp;expand=2210" rel="nofollow noreferrer">_mm512_dpbusds_epi32</a> intrinsic.  The instruction computes 8-bit dot products then adds them to a packed 32-bit accumulator (in this case with a saturating add).  The instruction is a bit unusual in that it both reads from and writes to the accumulator.  </p>
  
  <p>When compiled with gcc, the compiler is emitting extra move instructions (<code>vmovdqa64</code>) on the accumulator.  </p>
  
  <p>Here's a test program that accumulates some dot products:</p>
  
  <pre><code>#include &lt;immintrin.h&gt;
  #include &lt;cstddef&gt;
  __m512i Slow(const __m512i *a, const __m512i b0, const __m512i b1, std::size_t count) {
    __m512i c0 = _mm512_setzero_epi32();
    __m512i c1 = _mm512_setzero_epi32();
    for (std::size_t i = 0; i &lt; count; ++i) {
      c0 = _mm512_dpbusds_epi32(c0, a[i], b0);
      c1 = _mm512_dpbusds_epi32(c1, a[i], b1);
    }
    // Do not optimize away
    return _mm512_sub_epi32(c0, c1);
  }
  </code></pre>
  
  <p>When compiled with <code>g++ -O3 -mavx512vnni example.cc -S</code>, this is the main loop:</p>
  
  <pre><code>.L3:
    vmovdqa64 (%rdi), %zmm6
    vmovdqa64 %zmm3, %zmm0
    vmovdqa64 %zmm4, %zmm2
    addq  $64, %rdi
    vpdpbusds %zmm5, %zmm6, %zmm0
    vpdpbusds %zmm1, %zmm6, %zmm2
    vmovdqa64 %zmm0, %zmm3
    vmovdqa64 %zmm2, %zmm4
    cmpq  %rdi, %rax
    jne .L3
  
  </code></pre>
  
  <p>The above assembly is copying an accumulator from <code>zmm3</code> to <code>zmm0</code>, updating <code>zmm0</code>, and copying it back to <code>zmm3</code>.  This is unnecessary; it should just use one of <code>zmm0</code> or <code>zmm3</code> as an accumulator.  </p>
  
  <p>The problem is the same on <code>g++ (Gentoo 9.2.0-r2 p3) 9.2.0</code> and <code>g++ (Ubuntu 8.4.0-1ubuntu1~18.04) 8.4.0</code>.  </p>
  
  <p><code>clang++</code> 9.0.1 avoids the unnecessary copying (it also unrolled the loop, but here is the tightest version.)</p>
  
  <pre><code>.LBB0_6:                                # =&gt;This Inner Loop Header: Depth=1
    vmovaps (%rdi), %zmm4
    vpdpbusds %zmm0, %zmm4, %zmm3
    vpdpbusds %zmm1, %zmm4, %zmm2
    addq  $64, %rdi
    addq  $-1, %rax
    jne .LBB0_6
  </code></pre>
  
  <p>I was able to work around the problem in <code>g++</code> by using inline asm.  </p>
  
  <pre><code>#include &lt;immintrin.h&gt;
  #include &lt;cstddef&gt;
  __m512i Fast(const __m512i *a, const __m512i b0, const __m512i b1, std::size_t count) {
    __m512i c0 = _mm512_setzero_epi32();
    __m512i c1 = _mm512_setzero_epi32();
    for (std::size_t i = 0; i &lt; count; ++i) {
      asm ("vpdpbusds %2, %1, %0" : "+x"(c0) : "x"(a[i]), "mx"(b0));
      asm ("vpdpbusds %2, %1, %0" : "+x"(c1) : "x"(a[i]), "mx"(b1));
    }
    // Do not optimize away
    return _mm512_sub_epi32(c0, c1);
  }
  </code></pre>
  
  <p>The loop <code>g++</code> generates for <code>Fast</code> is much better:</p>
  
  <pre><code>.L3:
  #APP
  # 7 "asm.cc" 1
    vpdpbusds (%rdi), %zmm3, %zmm0
  # 0 "" 2
  # 8 "asm.cc" 1
    vpdpbusds (%rdi), %zmm1, %zmm2
  # 0 "" 2
  #NO_APP
    addq  $64, %rdi
    cmpq  %rax, %rdi
    jne .L3
  </code></pre>