使用gcc使用YMM指令添加数组

时间:2017-01-29 13:36:43

标签: gcc assembly x86 g++ avx

我想在gcc(AT& T语法)中运行以下代码(在Intel语法中)。

; float a[128], b[128], c[128];
; for (int i = 0; i < 128; i++) a[i] = b[i] + c[i];
; Assume that a, b and c are aligned by 32
      xor ecx, ecx              ; Loop counter i = 0
L:    vmovaps ymm0, [b+rcx]     ; Load 8 elements from b
      vaddps ymm0,ymm0,[c+rcx]  ; Add  8 elements from c    
      vmovaps [a+rcx], ymm0     ; Store result in a
      add ecx,32                ; 8 elements * 4 bytes = 32 
      cmp ecx, 512              ; 128 elements * 4 bytes = 512 
      jb L                      ;Loop

代码来自Optimizing subroutines in assembly language

到目前为止,我写的代码是:

static inline void addArray(float *a, float *b, float *c) {
__asm__ __volatile__ (
    "nop                            \n"
    "xor        %%ecx, %%ecx        \n" //;Loop counter set to 0
    "loop: \n\t"
    "vmovaps    %1, %%ymm0          \n" //;Load 8 elements from b  <== WRONG
    "vaddps     %2, %%ymm0, %%ymm0  \n" //;Add  8 elements from c  <==WRONG
    "vmovaps    %%ymm0, %0          \n" //;Store result in a
    "add        0x20, %%ecx         \n" //;8 elemtns * 4 bytes = 32 (0x20)
    "cmp        0x200,%%ecx         \n" //;128 elements * 4 bytes = 512 (0x200)
    "jb         loop                \n" //;Loop"
    "nop                            \n"
        : "=m"(a)                       //Outputs       
        : "m"(b), "m"(c)                //Inputs
        : "%ecx","%ymm0"                //Modifies ECX and YMM0
    );
}

标记为&#34;错误&#34; generate :(从gdb反汇编除外)

0x0000000000000b78 <+19>:   vmovaps -0x10(%rbp),%ymm0
0x0000000000000b7d <+24>:   vaddps -0x18(%rbp),%ymm0,%ymm0

我希望得到这样的东西(我猜):

vmovaps -0x10(%rbp,%ecx,%0x8),%ymm0

但我不知道如何指定%ecx作为我的索引寄存器。

请帮帮我吗?

修改

我已经尝试过(%1,%% ecx):

__asm__ __volatile__ (
    "nop                            \n"
    "xor        %%ecx, %%ecx        \n" //;Loop counter set to 0
    "loop: \n\t"
    "vmovaps    (%1, %%rcx), %%ymm0         \n" //;Load 8 elements from b  <== MODIFIED HERE
    "vaddps     %2, %%ymm0, %%ymm0  \n" //;Add  8 elements from c 
    "vmovaps    %%ymm0, %0          \n" //;Store result in a
    "add        0x20, %%ecx         \n" //;8 elemtns * 4 bytes = 32 (0x20)
    "cmp        0x200,%%ecx         \n" //;128 elements * 4 bytes = 512 (0x200)
    "jb         loop                \n" //;Loop"
    "nop                            \n"
        : "=m"(a)                       //Outputs       
        : "m"(b), "m"(c)                //Inputs
        : "%ecx","%ymm0"                //Modifies ECX and YMM0
    );

我得到了:

inline1.cpp: Assembler messages:
inline1.cpp:90: Error: found '(', expected: ')'
inline1.cpp:90: Error: junk `(%rbp),%rcx)' after expression

1 个答案:

答案 0 :(得分:4)

我认为不可能将字面翻译成GAS内联汇编。在AT&amp; T语法中,语法为:

displacement(base register, offset register, scalar multiplier)

会产生类似于:

的东西
movl  -4(%ebp, %ecx, 4), %eax

或在你的情况下:

vmovaps  -16(%rsp, %ecx, 0), %ymm0

问题是,当您使用内存约束(m)时,内联汇编程序将在您编写%n的任何位置发出以下内容(其中n是输入/输出):

-16(%rsp)

无法将上述内容操作为您真正想要的形式。你可以写:

(%1, %%rcx)

但这会产生:

(-16(%rsp),%rcx)

这显然是错误的。由于%n将整个-16(%rsp)作为一个块发出,因此无法获得它所属的那些括号中的偏移寄存器

当然,这不是一个真正的问题,因为你编写内联汇编来获得速度,并且从内存加载没有什么快速的。您应该在寄存器中输入,当您对输入/输出(r)使用寄存器约束时,您没有问题。请注意,这将需要稍微修改您的代码

您的内联程序集的其他错误包括:

  1. 数字文字以$开头。
  2. 说明应具有大小后缀,例如32位为l,64位为q
  3. 当你通过a书写时,你正在破坏记忆,所以你应该有一个memory clobber。
  4. 开头和结尾的nop指令完全没有意义。他们甚至没有调整分支目标。
  5. 除了新行(\t)之外,每一行都应该以制表符(\n)结束,以便在检查反汇编时获得正确对齐。
  6. 这是我的代码版本:

    void addArray(float *a, float *b, float *c) {
    __asm__ __volatile__ (
        "xorl       %%ecx, %%ecx                \n\t" // Loop counter set to 0
        "loop:                                  \n\t"
        "vmovaps    (%1,%%rcx), %%ymm0          \n\t" // Load 8 elements from b
        "vaddps     (%2,%%rcx), %%ymm0, %%ymm0  \n\t" // Add  8 elements from c
        "vmovaps    %%ymm0, (%0,%%rcx)          \n\t" // Store result in a
        "addl       $0x20,  %%ecx               \n\t" // 8 elemtns * 4 bytes = 32 (0x20)
        "cmpl       $0x200, %%ecx               \n\t" // 128 elements * 4 bytes = 512 (0x200)
        "jb         loop"                             // Loop"
            :                                         // Outputs       
            : "r" (a), "r" (b), "r" (c)               // Inputs
            : "%ecx", "%ymm0", "memory"               // Modifies ECX, YMM0, and memory
        );
    }
    

    这会导致编译器发出以下内容:

    addArray(float*, float*, float*):
            xorl       %ecx, %ecx                
         loop:                                  
            vmovaps    (%rsi,%rcx), %ymm0           # b
            vaddps     (%rdx,%rcx), %ymm0, %ymm0    # c
            vmovaps    %ymm0, (%rdi,%rcx)           # a
            addl       $0x20,  %ecx               
            cmpl       $0x200, %ecx               
            jb         loop
            vzeroupper
            retq
    

    或者,用更熟悉的英特尔语法:

    addArray(float*, float*, float*):
            xor     ecx, ecx
       loop:
            vmovaps ymm0, YMMWORD PTR [rsi + rcx]
            vaddps  ymm0, ymm0, YMMWORD PTR [rdx + rcx]
            vmovaps YMMWORD PTR [rdi + rcx], ymm0
            add     ecx, 32
            cmp     ecx, 512
            jb      loop
            vzeroupper
            ret
    

    在System V 64位调用约定中,前三个参数在rdirsirdx寄存器中传递,因此代码不需要移动将参数放入寄存器 - 它们已经存在。

    但是你没有充分利用输入/输出限制。您不需要需要 rcx作为计数器。您也不需要使用ymm0作为临时寄存器。如果让编译器选择使用哪些空闲寄存器,它将使代码更有效。您也不需要提供明确的clobber列表:

    #include <stdint.h>
    #include <x86intrin.h>
    
    void addArray(float *a, float *b, float *c) {
      uint64_t temp = 0;
      __m256   ymm;
      __asm__ __volatile__(
          "loop:                        \n\t"
          "vmovaps    (%3,%0), %1       \n\t" // Load 8 elements from b
          "vaddps     (%4,%0), %1, %1   \n\t" // Add  8 elements from c
          "vmovaps    %1, (%2,%0)       \n\t" // Store result in a
          "addl       $0x20,  %0        \n\t" // 8 elemtns * 4 bytes = 32 (0x20)
          "cmpl       $0x200, %0        \n\t" // 128 elements * 4 bytes = 512 (0x200)
          "jb         loop"                   // Loop
        : "+r" (temp), "=x" (ymm)
        : "r" (a), "r" (b), "r" (c)
        : "memory"
        );
    }
    

    当然,正如评论中所提到的,这整个练习都是浪费时间。 GAS风格的内联汇编虽然功能强大但是非常难以正确编写(我甚至没有100%肯定我的代码在这里是正确的!),所以你不应该使用内联汇编写任何内容你绝对不需要。这肯定不是你必须的情况 - 编译器会自动优化加法循环:

    void addArray(float *a, float *b, float *c) {
      for (int i = 0; i < 128; i++) a[i] = b[i] + c[i];
    }
    

    使用-O2-mavx2,GCC将其编译为以下内容:

    addArray(float*, float*, float*):
            xor     eax, eax
       .L2:
            vmovss  xmm0, DWORD PTR [rsi+rax]
            vaddss  xmm0, xmm0, DWORD PTR [rdx+rax]
            vmovss  DWORD PTR [rdi+rax], xmm0
            add     rax, 4
            cmp     rax, 512
            jne     .L2
            rep ret
    
    嗯,看起来非常熟悉,不是吗?公平地说,它不像你的代码那样被矢量化。你可以使用-O3-ftree-vectorize来获得,但你也得到a lot more code generated,所以我需要一个基准来说服我,它实际上更快,值得代码大小爆炸。但大部分是处​​理输入未对齐的情况 - if you indicate that it is aligned and that the pointer is restricted, that solves these problems and improves the code generation substantially。请注意,完全展开循环,并对添加进行矢量化。