我想在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
答案 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
)使用寄存器约束时,您没有问题。请注意,这将需要稍微修改您的代码
您的内联程序集的其他错误包括:
$
开头。l
,64位为q
。a
书写时,你正在破坏记忆,所以你应该有一个memory
clobber。nop
指令完全没有意义。他们甚至没有调整分支目标。\t
)之外,每一行都应该以制表符(\n
)结束,以便在检查反汇编时获得正确对齐。这是我的代码版本:
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位调用约定中,前三个参数在rdi
,rsi
和rdx
寄存器中传递,因此代码不需要移动将参数放入寄存器 - 它们已经存在。
但是你没有充分利用输入/输出限制。您不需要需要 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 restrict
ed, that solves these problems and improves the code generation substantially。请注意,完全展开循环,并对添加进行矢量化。