我很久以前就练习了Assembler,我想了解一个简单的程序(我从C代码生成汇编代码),它添加了2个向量(实际上是2个数组)并将结果存储在另一个向量(输出数组)中。我的目标是研究矢量化。为此,我在i7核心处理器上使用Debian Wheezy下的gcc-4.9。
这里是C代码片段(不是矢量化版本):
#include <stdio.h>
#define SIZE 10000
void test(double *a, double *b, double *c)
{
int i;
for (i = 0; i < SIZE; i++)
{
c[i] = a[i] + b[i];
}
}
int main()
{
int i;
double tab1[SIZE];
double tab2[SIZE];
double tab3[SIZE];
for (i = 0; i < SIZE; i++)
{
tab1[i] = i;
tab2[i] = i;
tab3[i] = 0;
}
test(tab1, tab2, tab3);
for (i = 0; i < SIZE; i++)
printf(" tab3[%d] = %f\n", i, tab3[i]);
return 0;
}
我使用AT&amp; T语法生成汇编代码:
gcc -std=c99 -c main_no_vectorized.c -O3 -S -o main_no_vectorized.s
这是汇编代码:
.file "main_no_vectorized.c"
.section .text.unlikely,"ax",@progbits
.LCOLDB0:
.text
.LHOTB0:
.p2align 4,,15
.globl test
.type test, @function
test:
.LFB3:
.cfi_startproc
leaq 16(%rdx), %rax
leaq 16(%rsi), %rcx
cmpq %rax, %rsi
setae %r8b
cmpq %rcx, %rdx
setae %cl
orb %cl, %r8b
je .L7
cmpq %rax, %rdi
leaq 16(%rdi), %rax
setae %cl
cmpq %rax, %rdx
setae %al
orb %al, %cl
je .L7
testb $8, %dil
pushq %r12
.cfi_def_cfa_offset 16
.cfi_offset 12, -16
pushq %rbp
.cfi_def_cfa_offset 24
.cfi_offset 6, -24
pushq %rbx
.cfi_def_cfa_offset 32
.cfi_offset 3, -32
je .L8
movsd (%rdi), %xmm0
movl $9998, %ebp
movl $4999, %r9d
movl $9999, %r12d
movl $1, %r8d
movl $1, %ebx
addsd (%rsi), %xmm0
movsd %xmm0, (%rdx)
.L3:
salq $3, %r8
xorl %eax, %eax
xorl %ecx, %ecx
leaq (%rdi,%r8), %r11
leaq (%rsi,%r8), %r10
addq %rdx, %r8
.p2align 4,,10
.p2align 3
.L4:
movupd (%r10,%rax), %xmm0
addl $1, %ecx
addpd (%r11,%rax), %xmm0
movups %xmm0, (%r8,%rax)
addq $16, %rax
cmpl %r9d, %ecx
jb .L4
cmpl %ebp, %r12d
leal (%rbx,%rbp), %eax
je .L1
cltq
movsd (%rdi,%rax,8), %xmm0
addsd (%rsi,%rax,8), %xmm0
movsd %xmm0, (%rdx,%rax,8)
.L1:
popq %rbx
.cfi_remember_state
.cfi_restore 3
.cfi_def_cfa_offset 24
popq %rbp
.cfi_restore 6
.cfi_def_cfa_offset 16
popq %r12
.cfi_restore 12
.cfi_def_cfa_offset 8
ret
.p2align 4,,10
.p2align 3
.L8:
.cfi_restore_state
movl $10000, %ebp
movl $5000, %r9d
movl $10000, %r12d
xorl %r8d, %r8d
xorl %ebx, %ebx
jmp .L3
.L7:
.cfi_def_cfa_offset 8
.cfi_restore 3
.cfi_restore 6
.cfi_restore 12
xorl %eax, %eax
.p2align 4,,10
.p2align 3
.L2:
movsd (%rdi,%rax), %xmm0
addsd (%rsi,%rax), %xmm0
movsd %xmm0, (%rdx,%rax)
addq $8, %rax
cmpq $80000, %rax
jne .L2
rep ret
.cfi_endproc
.LFE3:
.size test, .-test
.section .text.unlikely
.LCOLDE0:
.text
.LHOTE0:
.section .rodata.str1.1,"aMS",@progbits,1
.LC3:
.string " tab3[%d] = %f\n"
.section .text.unlikely
.LCOLDB4:
.section .text.startup,"ax",@progbits
.LHOTB4:
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB4:
.cfi_startproc
pushq %rbx
.cfi_def_cfa_offset 16
.cfi_offset 3, -16
xorl %eax, %eax
subq $240016, %rsp
.cfi_def_cfa_offset 240032
movdqa .LC2(%rip), %xmm3
leaq 32(%rsp), %rcx
leaq 80032(%rsp), %rdx
movdqa .LC1(%rip), %xmm1
.p2align 4,,10
.p2align 3
.L21:
pshufd $238, %xmm1, %xmm0
cvtdq2pd %xmm1, %xmm2
paddd %xmm3, %xmm1
movaps %xmm2, 16(%rsp,%rax)
cvtdq2pd %xmm0, %xmm0
movaps %xmm2, 80016(%rsp,%rax)
movaps %xmm0, (%rcx,%rax)
movaps %xmm0, (%rdx,%rax)
addq $32, %rax
cmpq $80000, %rax
jne .L21
leaq 160016(%rsp), %rdi
movl $80000, %edx
xorl %esi, %esi
call memset
xorl %eax, %eax
.p2align 4,,10
.p2align 3
.L22:
movapd 16(%rsp,%rax), %xmm0
addpd 80016(%rsp,%rax), %xmm0
movaps %xmm0, 160016(%rsp,%rax)
addq $16, %rax
cmpq $80000, %rax
jne .L22
xorl %ebx, %ebx
.p2align 4,,10
.p2align 3
.L23:
movsd 160016(%rsp,%rbx,8), %xmm4
movl %ebx, %esi
movl $.LC3, %edi
movl $1, %eax
addq $1, %rbx
movapd %xmm4, %xmm0
movsd %xmm4, 8(%rsp)
call printf
cmpq $10000, %rbx
jne .L23
addq $240016, %rsp
.cfi_def_cfa_offset 16
xorl %eax, %eax
popq %rbx
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE4:
.size main, .-main
.section .text.unlikely
.LCOLDE4:
.section .text.startup
.LHOTE4:
.section .rodata.cst16,"aM",@progbits,16
.align 16
.LC1:
.long 0
.long 1
.long 2
.long 3
.align 16
.LC2:
.long 4
.long 4
.long 4
.long 4
.ident "GCC: (Debian 4.9.1-16) 4.9.1"
.section .note.GNU-stack,"",@progbits
您能否向我解释上述汇编代码与C代码相关的主要步骤,特别是&#34; test&#34;函数,主函数初始化循环和参数传递(即堆栈的推送和弹出指令)以及&#34; a&#34;和&#34; b&#34;数组?
什么对应.L2,.L3,...段?与L2缓存,L3缓存有关系吗?
很抱歉这些基本问题,但我从Intel x86_64汇编程序开始。
感谢您的宝贵帮助
答案 0 :(得分:1)
生成的汇编代码非常复杂。它首先检查数组a,b和c是否以导致优化循环失败的方式重叠。例如,如果您这样做:
test(tab1, tab2, &tab1[1]);
然后检测到重叠并使代码跳转到L7(直接实现)。顺便说一下,L代表Label,标签号只是编译器生成的,没有特别的意义。所以L1,L2,L3等只是用于代码分支到不同位置的标签。重叠检查从.LFB3
开始,到最后je .L7
结束。
如果未检测到重叠,则将使用优化的循环。这个优化的循环将尝试一次添加两个双精度而不是一个。优化循环的第一件事是找出数组a
是否与16字节边界(testb $8, %dil
指令)对齐。如果是,它将跳转到L8以加载一组常量(例如r9 = 5000)。如果数组未对齐,if将通过并加载一组不同的常量(例如r9 = 4999),并处理第一个元素。这是因为未对齐的情况需要一次进行两次4999次迭代,并在循环外单独处理第一个和最后一个未对齐的元素。对齐的案例将只进行5000次迭代。
无论哪种方式,代码接下来达到L3。 L3和L4处的代码是优化的循环,它使用addpd
指令一次添加两个(L7中的非优化循环使用addsd
一次添加一个)。在L4循环结束后,它会检查是否需要处理最后一个元素(对于未对齐的情况)。然后它返回ret
指令。
顺便说一下,知道调用test
时,a
位于rdi
,b
位于rsi
,而{{} 1}}在c
中。这是64位的调用约定。因此,堆栈上没有任何参数。如果您不太了解x86程序集,请专注于从L7开始的代码。这是非优化版本,你应该能够计算出那个部分,因为我说你的三个参数都在rdi,rsi和rdx中。
答案 1 :(得分:0)
.L2
等标签,它们用于指代下一条指令。如果您使用goto
,它们就像C中的标签一样。标签的主要用途是使用跳转或分支来指定跳转的位置。
例如,.L2
标签是for (i = 0; i < SIZE; i++)
中test()
循环正文的开头,它按8个字节计算(double
的大小)最高8 * 10000。循环中的最后一条指令是jne .L2
,如果先前的比较不相等,则跳转到.L2
。
您可能会在x64上找到this reference (PDF)帮助。