va_alist ( using variable list in 64 bit machine ) - c

I am trying to implement a print function in the kernel module for my learning purposes. I am emulating it on QEMU.
#define va_alist __builtin_va_alist
#define va_dcl __builtin_va_list_t __builtin_va_list; ...
#define va_start(ap) __builtin_varargs_start(ap)
#define va_arg(ap, type) __builtin_va_arg((ap), type)
#define va_end(ap) __builtin_va_end(ap)
But I am getting the error that __builtin_va_alist is undeclared. Should I try to find the definition of __builtin_va_alist also and put it in my include file or am I not aware of something here? Also, If i change __builtin_va_alist to __builtin_va_list ( note: a is not there ), then I am getting an error called implicit declaration of __builtin_varargs_start . Kindly help.
Thanks
Chidambaram

How varargs works on x86-64 is actually fairly complicated.
If we take this as an example:
#include <stdio.h>
int main()
{
double f=0.7;
printf("%d %f %p %d %f", 17, f, "hello", 42, 0.8);
return 0;
}
The code it generates is:
.file "printf.c"
.section .rodata.str1.1,"aMS",#progbits,1
.LC1:
.string "hello"
.LC3:
.string "%d %f %p %d %f"
.section .text.startup,"ax",#progbits
.p2align 4,,15
.globl main
.type main, #function
main:
.LFB11:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $42, %ecx
movl $.LC1, %edx
movsd .LC0(%rip), %xmm1
movl $17, %esi
movsd .LC2(%rip), %xmm0
movl $.LC3, %edi
movl $2, %eax
call printf
xorl %eax, %eax
addq $8, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE11:
.size main, .-main
.section .rodata.cst8,"aM",#progbits,8
.align 8
.LC0:
.long 2576980378
.long 1072273817
.align 8
.LC2:
.long 1717986918
.long 1072064102
.ident "GCC: (GNU) 4.6.3 20120306 (Red Hat 4.6.3-2)"
.section .note.GNU-stack,"",#progbits
As you can see, the floating point values are held in %xmm0 and %xmm1, and the printf function (like any other varargs function) is "told" how many arguments are passed in SSE registers by the value in %eax (2 in this case). Regular arguments are passed in registers, so %edi, %esi, %edx, %ecx contain the format string, the first integer argument, the address of "hello" and the second integer argument. This follows the standard argument ordering of x86_64.
The compiler normally generates code to then push all the argument registers on the stack, and "fish out" the registers in the va* functions.
So if we take the above source code and replace the printf with a myprintf, which looks like this:
void myprintf(const char *fmt, ...)
{
va_list va;
int i;
va_start(va, fmt);
for(i = 0; i < 5; i++)
{
switch(i)
{
case 1:
case 4:
{
double d = va_arg(va, double);
printf("double %f:", d);
}
break;
default:
{
long l = va_arg(va, long);
printf("long %ld:", l);
}
}
}
printf("\n");
}
at the beginning of myprintf it does:
...
movq %rsi, 40(%rsp)
movq %rdx, 48(%rsp)
movq %rcx, 56(%rsp)
movq %r8, 64(%rsp)
movq %r9, 72(%rsp)
je .L2
movaps %xmm0, 80(%rsp)
movaps %xmm1, 96(%rsp)
movaps %xmm2, 112(%rsp)
movaps %xmm3, 128(%rsp)
movaps %xmm4, 144(%rsp)
movaps %xmm5, 160(%rsp)
movaps %xmm6, 176(%rsp)
movaps %xmm7, 192(%rsp)
.L2:
...
The code to then fish things out of the stack is quite complicated. This is the floating point side:
.L4:
.cfi_restore_state
movl 12(%rsp), %edx
cmpl $176, %edx
jae .L5
movl %edx, %eax
addq 24(%rsp), %rax
addl $16, %edx
movl %edx, 12(%rsp)
.L6:
movsd (%rax), %xmm0
movl $.LC0, %edi
movl $1, %eax
call printf
jmp .L7
.p2align 4,,10
.p2align 3
.L8:
movq 16(%rsp), %rax
leaq 8(%rax), %rdx
movq %rdx, 16(%rsp)
jmp .L9
.p2align 4,,10
.p2align 3
.L5:
movq 16(%rsp), %rax
leaq 8(%rax), %rdx
movq %rdx, 16(%rsp)
jmp .L6
Now, I don't know what compiler flags you are using, because my compiler generates this code with gcc -O2 -nostdlib -fno-builtin -ffreestanding without any problem.

Related

Why GCC -Ofast makes the program wrong but only when it prints the result twice?

Recompiling an old program made it output the wrong result. I'd like to know why.
I know that -Ofast may "disregard strict standards compliance" but I'm curious about what happens under the hood.
I reduced the program to this minimal example foo1.c:
#include <stdio.h>
double my_pow(double x, unsigned n)
{ /* returns x^n */
double y = 1;
while(n--) y *= x;
return y;
}
void foo(double small)
{ /* prints small^19 */
double x = my_pow(small,19);
printf("%E\n",x);
printf("%E\n",x);
}
int main(void)
{
foo(1-0.8-0.2);
return 0;
}
When compiled with -Ofast it gives a different output than with any other optimization level.
gcc -Ofast foo1.c && ./a.out:
-0.000000E+00
-0.000000E+00
gcc foo1.c && ./a.out:
-1.390671E-309
-1.390671E-309
A strange fact is that when one of the printf is commented out (file foo2.c) this behavior doesn't replicate making it a sort of heisenbug.
gcc -Ofast foo2.c && ./a.out:
-1.390671E-309
gcc foo2.c && ./a.out:
-1.390671E-309
Informations that might be useful:
gcc -v:
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/lto-wrapper
Target: x86_64-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-linker-hash-style=gnu --enable-languages=c,c++,objc,obj-c++,java,fortran,ada,go,lto --enable-plugin --enable-initfini-array --disable-libgcj --with-isl=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/isl-install --with-cloog=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/cloog-install --enable-gnu-indirect-function --with-tune=generic --with-arch_32=x86-64 --build=x86_64-redhat-linux
Thread model: posix
gcc version 4.8.5 20150623 (Red Hat 4.8.5-39) (GCC)
gcc -Ofast foo1.c -S -o -:
.file "foo1.c"
.text
.p2align 4,,15
.globl my_pow
.type my_pow, #function
my_pow:
.LFB11:
.cfi_startproc
testl %edi, %edi
leal -1(%rdi), %edx
je .L10
movl %edi, %ecx
shrl %ecx
movl %ecx, %esi
addl %esi, %esi
je .L11
cmpl $9, %edi
jbe .L11
movapd %xmm0, %xmm1
movapd .LC0(%rip), %xmm2
xorl %eax, %eax
unpcklpd %xmm1, %xmm1
.L9:
addl $1, %eax
mulpd %xmm1, %xmm2
cmpl %eax, %ecx
ja .L9
movapd %xmm2, -24(%rsp)
subl %esi, %edx
cmpl %esi, %edi
movsd -16(%rsp), %xmm1
mulsd %xmm2, %xmm1
je .L2
testl %edx, %edx
mulsd %xmm0, %xmm1
je .L2
.L35:
cmpl $1, %edx
mulsd %xmm0, %xmm1
je .L2
cmpl $2, %edx
mulsd %xmm0, %xmm1
je .L2
cmpl $3, %edx
mulsd %xmm0, %xmm1
je .L2
cmpl $4, %edx
mulsd %xmm0, %xmm1
je .L2
cmpl $5, %edx
mulsd %xmm0, %xmm1
je .L2
cmpl $6, %edx
mulsd %xmm0, %xmm1
je .L2
cmpl $7, %edx
mulsd %xmm0, %xmm1
je .L2
mulsd %xmm0, %xmm1
.p2align 4,,10
.p2align 3
.L2:
movapd %xmm1, %xmm0
ret
.p2align 4,,10
.p2align 3
.L11:
movsd .LC1(%rip), %xmm1
testl %edx, %edx
mulsd %xmm0, %xmm1
je .L2
jmp .L35
.p2align 4,,10
.p2align 3
.L10:
movsd .LC1(%rip), %xmm1
jmp .L2
.cfi_endproc
.LFE11:
.size my_pow, .-my_pow
.section .rodata.str1.1,"aMS",#progbits,1
.LC2:
.string "%E\n"
.text
.p2align 4,,15
.globl foo
.type foo, #function
foo:
.LFB12:
.cfi_startproc
movapd %xmm0, %xmm2
subq $24, %rsp
.cfi_def_cfa_offset 32
movl $.LC2, %edi
movl $1, %eax
unpcklpd %xmm2, %xmm2
movapd %xmm2, %xmm1
mulpd %xmm2, %xmm1
mulpd %xmm1, %xmm1
mulpd %xmm1, %xmm1
mulpd %xmm2, %xmm1
movapd %xmm1, %xmm2
unpckhpd %xmm1, %xmm1
mulsd %xmm1, %xmm2
mulsd %xmm0, %xmm2
movapd %xmm2, %xmm0
movsd %xmm2, 8(%rsp)
call printf
movsd 8(%rsp), %xmm2
movl $.LC2, %edi
movl $1, %eax
addq $24, %rsp
.cfi_def_cfa_offset 8
movapd %xmm2, %xmm0
jmp printf
.cfi_endproc
.LFE12:
.size foo, .-foo
.section .text.startup,"ax",#progbits
.p2align 4,,15
.globl main
.type main, #function
main:
.LFB13:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movsd .LC3(%rip), %xmm0
call foo
xorl %eax, %eax
addq $8, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE13:
.size main, .-main
.section .rodata.cst16,"aM",#progbits,16
.align 16
.LC0:
.long 0
.long 1072693248
.long 0
.long 1072693248
.section .rodata.cst8,"aM",#progbits,8
.align 8
.LC1:
.long 0
.long 1072693248
.align 8
.LC3:
.long 0
.long -1131413504
.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-39)"
.section .note.GNU-stack,"",#progbits
gcc foo1.c -S -o -:
.file "foo1.c"
.text
.globl my_pow
.type my_pow, #function
my_pow:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movsd %xmm0, -24(%rbp)
movl %edi, -28(%rbp)
movabsq $4607182418800017408, %rax
movq %rax, -8(%rbp)
jmp .L2
.L3:
movsd -8(%rbp), %xmm0
mulsd -24(%rbp), %xmm0
movsd %xmm0, -8(%rbp)
.L2:
movl -28(%rbp), %eax
leal -1(%rax), %edx
movl %edx, -28(%rbp)
testl %eax, %eax
jne .L3
movq -8(%rbp), %rax
movq %rax, -40(%rbp)
movsd -40(%rbp), %xmm0
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size my_pow, .-my_pow
.section .rodata
.LC1:
.string "%E\n"
.text
.globl foo
.type foo, #function
foo:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movsd %xmm0, -24(%rbp)
movq -24(%rbp), %rax
movl $19, %edi
movq %rax, -32(%rbp)
movsd -32(%rbp), %xmm0
call my_pow
movsd %xmm0, -32(%rbp)
movq -32(%rbp), %rax
movq %rax, -8(%rbp)
movq -8(%rbp), %rax
movq %rax, -32(%rbp)
movsd -32(%rbp), %xmm0
movl $.LC1, %edi
movl $1, %eax
call printf
movq -8(%rbp), %rax
movq %rax, -32(%rbp)
movsd -32(%rbp), %xmm0
movl $.LC1, %edi
movl $1, %eax
call printf
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size foo, .-foo
.globl main
.type main, #function
main:
.LFB2:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movabsq $-4859383997932765184, %rax
movq %rax, -8(%rbp)
movsd -8(%rbp), %xmm0
call foo
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE2:
.size main, .-main
.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-39)"
.section .note.GNU-stack,"",#progbits
gcc -Ofast foo2.c -S -o -:
.file "foo2.c"
.text
.p2align 4,,15
.globl my_pow
.type my_pow, #function
my_pow:
.LFB11:
.cfi_startproc
testl %edi, %edi
leal -1(%rdi), %edx
je .L10
movl %edi, %ecx
shrl %ecx
movl %ecx, %esi
addl %esi, %esi
je .L11
cmpl $9, %edi
jbe .L11
movapd %xmm0, %xmm1
movapd .LC0(%rip), %xmm2
xorl %eax, %eax
unpcklpd %xmm1, %xmm1
.L9:
addl $1, %eax
mulpd %xmm1, %xmm2
cmpl %eax, %ecx
ja .L9
movapd %xmm2, -24(%rsp)
subl %esi, %edx
cmpl %esi, %edi
movsd -16(%rsp), %xmm1
mulsd %xmm2, %xmm1
je .L2
testl %edx, %edx
mulsd %xmm0, %xmm1
je .L2
.L35:
cmpl $1, %edx
mulsd %xmm0, %xmm1
je .L2
cmpl $2, %edx
mulsd %xmm0, %xmm1
je .L2
cmpl $3, %edx
mulsd %xmm0, %xmm1
je .L2
cmpl $4, %edx
mulsd %xmm0, %xmm1
je .L2
cmpl $5, %edx
mulsd %xmm0, %xmm1
je .L2
cmpl $6, %edx
mulsd %xmm0, %xmm1
je .L2
cmpl $7, %edx
mulsd %xmm0, %xmm1
je .L2
mulsd %xmm0, %xmm1
.p2align 4,,10
.p2align 3
.L2:
movapd %xmm1, %xmm0
ret
.p2align 4,,10
.p2align 3
.L11:
movsd .LC1(%rip), %xmm1
testl %edx, %edx
mulsd %xmm0, %xmm1
je .L2
jmp .L35
.p2align 4,,10
.p2align 3
.L10:
movsd .LC1(%rip), %xmm1
jmp .L2
.cfi_endproc
.LFE11:
.size my_pow, .-my_pow
.section .rodata.str1.1,"aMS",#progbits,1
.LC2:
.string "%E\n"
.text
.p2align 4,,15
.globl foo
.type foo, #function
foo:
.LFB12:
.cfi_startproc
movapd %xmm0, %xmm2
movl $.LC2, %edi
movl $1, %eax
unpcklpd %xmm2, %xmm2
movapd %xmm2, %xmm1
mulpd %xmm2, %xmm1
mulpd %xmm1, %xmm1
mulpd %xmm1, %xmm1
mulpd %xmm2, %xmm1
movapd %xmm1, %xmm2
unpckhpd %xmm1, %xmm1
mulsd %xmm1, %xmm2
mulsd %xmm0, %xmm2
movapd %xmm2, %xmm0
jmp printf
.cfi_endproc
.LFE12:
.size foo, .-foo
.section .text.startup,"ax",#progbits
.p2align 4,,15
.globl main
.type main, #function
main:
.LFB13:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $.LC2, %edi
movl $1, %eax
movsd .LC3(%rip), %xmm0
call printf
xorl %eax, %eax
addq $8, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE13:
.size main, .-main
.section .rodata.cst16,"aM",#progbits,16
.align 16
.LC0:
.long 0
.long 1072693248
.long 0
.long 1072693248
.section .rodata.cst8,"aM",#progbits,8
.align 8
.LC1:
.long 0
.long 1072693248
.align 8
.LC3:
.long 0
.long -2147418112
.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-39)"
.section .note.GNU-stack,"",#progbits
gcc foo2.c -S -o -:
.file "foo2.c"
.text
.globl my_pow
.type my_pow, #function
my_pow:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movsd %xmm0, -24(%rbp)
movl %edi, -28(%rbp)
movabsq $4607182418800017408, %rax
movq %rax, -8(%rbp)
jmp .L2
.L3:
movsd -8(%rbp), %xmm0
mulsd -24(%rbp), %xmm0
movsd %xmm0, -8(%rbp)
.L2:
movl -28(%rbp), %eax
leal -1(%rax), %edx
movl %edx, -28(%rbp)
testl %eax, %eax
jne .L3
movq -8(%rbp), %rax
movq %rax, -40(%rbp)
movsd -40(%rbp), %xmm0
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size my_pow, .-my_pow
.section .rodata
.LC1:
.string "%E\n"
.text
.globl foo
.type foo, #function
foo:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movsd %xmm0, -24(%rbp)
movq -24(%rbp), %rax
movl $19, %edi
movq %rax, -32(%rbp)
movsd -32(%rbp), %xmm0
call my_pow
movsd %xmm0, -32(%rbp)
movq -32(%rbp), %rax
movq %rax, -8(%rbp)
movq -8(%rbp), %rax
movq %rax, -32(%rbp)
movsd -32(%rbp), %xmm0
movl $.LC1, %edi
movl $1, %eax
call printf
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size foo, .-foo
.globl main
.type main, #function
main:
.LFB2:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movabsq $-4859383997932765184, %rax
movq %rax, -8(%rbp)
movsd -8(%rbp), %xmm0
call foo
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE2:
.size main, .-main
.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-39)"
.section .note.GNU-stack,"",#progbits
Under -ffast-math (and it's siblings like -Ofast) gcc links your app with a special startup code in crtfastmath.c which sets flush-to-zero flag:
static void __attribute__((constructor))
set_fast_math (void)
{
#ifndef __x86_64__
...
#else
unsigned int mxcsr = __builtin_ia32_stmxcsr ();
mxcsr |= MXCSR_DAZ | MXCSR_FTZ;
__builtin_ia32_ldmxcsr (mxcsr);
#endif
}
(from here).

Loop ordering and -ftree-vectorize

I have four nested for loops as follows:
const size_t nMax = 300 ; double acc = 0 ;
for( size_t i=0 ; i<nMax ; i++ ) {
for( size_t j=0 ; j<nMax ; j++ ) {
for( size_t k=0 ; k<nMax ; k++ ) {
for( size_t l=k ; l<nMax ; l++ ) {
acc += 1.0 ;
}
}
}
}
printf( "Accumulator is %f\n", acc ) ;
Compiling with gcc (version 5.4.0) and -O3, the code completes in 5.188 seconds. Now, if I reorder the loops
const size_t nMax = 300 ; double acc = 0 ;
for( size_t k=0 ; k<nMax ; k++ ) {
for( size_t l=k ; l<nMax ; l++ ) {
for( size_t i=0 ; i<nMax ; i++ ) {
for( size_t j=0 ; j<nMax ; j++ ) {
acc += 1.0 ;
}
}
}
}
printf( "Accumulator is %f\n", acc ) ;
and once again compile with -O3, the code runs in 3.500 seconds. I'm uncertain why this is the case: I get that in the second code segment, the loop over l checks the value of the outer loop for the start condition nMax * nMax times less frequently than in the first code segment, but it seems unlikely (?) that this would yield such a significant performance hit.
Compiling with -O2 makes both codes execute in the same amount of time (roughly 5.2 seconds), so I managed to track down -ftree-vectorize as the flag that can be used in conjunction with -O2 to recover the performance difference.
Why can -ftree-vectorize optimise the second code segment and not the first?
Edit
Added the assembly versions of the code with gcc -O2 -ftree-vectorize. I'm fairly ignorant on assembler, so can't really claim to understand a great deal. Here's the first (slow) segment:
.file "code_one.c"
.section .rodata.str1.1,"aMS",#progbits,1
.LC2:
.string "Accumulator is %f\n"
.section .text.unlikely,"ax",#progbits
.LCOLDB3:
.section .text.startup,"ax",#progbits
.LHOTB3:
.p2align 4,,15
.globl main
.type main, #function
main:
.LFB38:
.cfi_startproc
pxor %xmm0, %xmm0
xorl %ecx, %ecx
movapd .LC1(%rip), %xmm1
.L2:
movq %rcx, %rsi
.L8:
movhpd .LC0(%rip), %xmm0
xorl %edx, %edx
.p2align 4,,10
.p2align 3
.L6:
movl $300, %eax
.p2align 4,,10
.p2align 3
.L3:
subq $1, %rax
addpd %xmm1, %xmm0
jne .L3
addl $1, %edx
cmpl $150, %edx
jne .L6
movdqa %xmm0, %xmm2
addq $1, %rsi
cmpq $300, %rsi
psrldq $8, %xmm2
addpd %xmm2, %xmm0
jne .L8
addq $1, %rcx
cmpq $300, %rcx
jne .L2
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $.LC2, %esi
movl $1, %edi
movl $1, %eax
call __printf_chk
xorl %eax, %eax
addq $8, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE38:
.size main, .-main
.section .text.unlikely
.LCOLDE3:
.section .text.startup
.LHOTE3:
.section .rodata.cst8,"aM",#progbits,8
.align 8
.LC0:
.long 0
.long 0
.section .rodata.cst16,"aM",#progbits,16
.align 16
.LC1:
.long 0
.long 1072693248
.long 0
.long 1072693248
.ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609"
.section .note.GNU-stack,"",#progbits
and the second (fast) version:
.file "code_two.c"
.section .rodata.str1.1,"aMS",#progbits,1
.LC2:
.string "Accumulator is %f\n"
.section .text.unlikely,"ax",#progbits
.LCOLDB3:
.section .text.startup,"ax",#progbits
.LHOTB3:
.p2align 4,,15
.globl main
.type main, #function
main:
.LFB38:
.cfi_startproc
pxor %xmm0, %xmm0
movl $300, %ecx
movsd .LC1(%rip), %xmm1
.L2:
movl $300, %esi
.L8:
xorl %edx, %edx
.p2align 4,,10
.p2align 3
.L6:
movq %rdx, %rax
.p2align 4,,10
.p2align 3
.L3:
addq $1, %rax
addsd %xmm1, %xmm0
cmpq $300, %rax
jne .L3
addq $1, %rdx
cmpq $300, %rdx
jne .L6
subq $1, %rsi
jne .L8
subq $1, %rcx
jne .L2
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $.LC2, %esi
movl $1, %edi
movl $1, %eax
call __printf_chk
xorl %eax, %eax
addq $8, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE38:
.size main, .-main
.section .text.unlikely
.LCOLDE3:
.section .text.startup
.LHOTE3:
.section .rodata.cst8,"aM",#progbits,8
.align 8
.LC1:
.long 0
.long 1072693248
.ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609"
.section .note.GNU-stack,"",#progbits

Why does summing floats into an int temporary run so much slower than when everything is int?

I compiled this program in GCC 4.8 -O2 on Skylake 6700HQ.
When I use float data type, total execution time is 0.000176 sec. When I change the float to int, the total time is 0.000026 (~7x faster). I don't know the reason for this difference. Related question: assembly output O3
I use this command in Geany IDE build command gcc -Wall -march=native -O2 -o "%e" "%f". I also tried -O3 and -Ofast, but those do not fix the problem.
I also read this question but there is too much differences between this float and int implementation. Since this float implementation is 7 times slower than the corresponding int implementation, this is not a duplicate question
#include <stdio.h>
#include <time.h>
float a[32][32]
, t[32][32]
, c_result[32][32]
, c_tra[32][32] ;
int main()
{
int w = 10000;
int i, j, k, temp;
struct timespec tStart, tEnd;
double tTotal , tBest=10000;
do{
clock_gettime(CLOCK_MONOTONIC,&tStart);
for( i = 0; i < 32; i++){
for( j =0 ; j < 32; j++){
temp=0;
for( k = 0 ;k < 32; k++) {
temp += a[i][k] * c_tra[j][k];
}
c_result[i][j]= temp;
}
}
clock_gettime(CLOCK_MONOTONIC,&tEnd);
tTotal = (tEnd.tv_sec - tStart.tv_sec);
tTotal += (tEnd.tv_nsec - tStart.tv_nsec) / 1000000000.0;
if(tTotal<tBest)
tBest=tTotal;
}while(w--);
printf(" The best time: %lf sec\n",tBest);
return 0;
}
It is assembly out put for int data type:
.file "floatMULm.c"
.section .rodata.str1.8,"aMS",#progbits,1
.align 8
.LC2:
.string " The best time: %lf sec in %d repetition for %dX%d matrix\n"
.section .text.startup,"ax",#progbits
.p2align 4,,15
.globl main
.type main, #function
main:
.LFB24:
.cfi_startproc
subq $40, %rsp
.cfi_def_cfa_offset 48
movl $1, %edi
movq %rsp, %rsi
call clock_gettime
xorl %esi, %esi
.L2:
xorl %r8d, %r8d
.p2align 4,,10
.p2align 3
.L7:
movq %r8, %rdi
xorl %eax, %eax
xorl %ecx, %ecx
salq $5, %rdi
.p2align 4,,10
.p2align 3
.L5:
movl a(%rsi,%rax), %edx
imull c_tra(%rdi,%rax), %edx
addq $4, %rax
addl %edx, %ecx
cmpq $128, %rax
jne .L5
movl %ecx, c_result(%rsi,%r8)
addq $4, %r8
cmpq $128, %r8
jne .L7
subq $-128, %rsi
cmpq $4096, %rsi
jne .L2
leaq 16(%rsp), %rsi
movl $1, %edi
call clock_gettime
movq 24(%rsp), %rax
subq 8(%rsp), %rax
movl $32, %r8d
movl $32, %ecx
movl $10000, %edx
movl $.LC2, %esi
movl $1, %edi
vcvtsi2sdq %rax, %xmm1, %xmm1
movq 16(%rsp), %rax
subq (%rsp), %rax
vcvtsi2sdq %rax, %xmm0, %xmm0
movl $1, %eax
vdivsd .LC1(%rip), %xmm1, %xmm1
vaddsd %xmm0, %xmm1, %xmm0
vminsd .LC0(%rip), %xmm0, %xmm0
call __printf_chk
xorl %eax, %eax
addq $40, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE24:
.size main, .-main
.comm c_tra,4096,32
.comm c_result,4096,32
.comm t,4096,32
.comm a,4096,32
.section .rodata.cst8,"aM",#progbits,8
.align 8
.LC0:
.long 0
.long 1086556160
.align 8
.LC1:
.long 0
.long 1104006501
.ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.1) 4.8.4"
.section .note.GNU-stack,"",#progbits
And this is for float :
.file "floatMULm.c"
.section .rodata.str1.8,"aMS",#progbits,1
.align 8
.LC2:
.string " The best time: %lf sec in %d repetition for %dX%d matrix\n"
.section .text.startup,"ax",#progbits
.p2align 4,,15
.globl main
.type main, #function
main:
.LFB24:
.cfi_startproc
subq $40, %rsp
.cfi_def_cfa_offset 48
movl $1, %edi
movq %rsp, %rsi
call clock_gettime
xorl %ecx, %ecx
.L2:
xorl %edi, %edi
.p2align 4,,10
.p2align 3
.L7:
movq %rdi, %rsi
xorl %eax, %eax
xorl %edx, %edx
salq $5, %rsi
.p2align 4,,10
.p2align 3
.L5:
vcvtsi2ss %edx, %xmm0, %xmm0
vmovss a(%rcx,%rax), %xmm2
vfmadd231ss c_tra(%rsi,%rax), %xmm2, %xmm0
addq $4, %rax
vcvttss2si %xmm0, %edx
cmpq $128, %rax
jne .L5
vcvtsi2ss %edx, %xmm0, %xmm0
vmovss %xmm0, c_result(%rcx,%rdi)
addq $4, %rdi
cmpq $128, %rdi
jne .L7
subq $-128, %rcx
cmpq $4096, %rcx
jne .L2
leaq 16(%rsp), %rsi
movl $1, %edi
call clock_gettime
movq 24(%rsp), %rax
subq 8(%rsp), %rax
movl $32, %r8d
movl $32, %ecx
movl $10000, %edx
movl $.LC2, %esi
movl $1, %edi
vcvtsi2sdq %rax, %xmm1, %xmm1
movq 16(%rsp), %rax
subq (%rsp), %rax
vcvtsi2sdq %rax, %xmm0, %xmm0
movl $1, %eax
vdivsd .LC1(%rip), %xmm1, %xmm1
vaddsd %xmm0, %xmm1, %xmm0
vminsd .LC0(%rip), %xmm0, %xmm0
call __printf_chk
xorl %eax, %eax
addq $40, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE24:
.size main, .-main
.comm c_tra,4096,32
.comm c_result,4096,32
.comm t,4096,32
.comm a,4096,32
.section .rodata.cst8,"aM",#progbits,8
.align 8
.LC0:
.long 0
.long 1086556160
.align 8
.LC1:
.long 0
.long 1104006501
.ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.1) 4.8.4"
.section .note.GNU-stack,"",#progbits
The problem is the inner loop of the floating-point version:
.L5:
vcvtsi2ss %edx, %xmm0, %xmm0
vmovss a(%rcx,%rax), %xmm2
vfmadd231ss c_tra(%rsi,%rax), %xmm2, %xmm0
addq $4, %rax
vcvttss2si %xmm0, %edx
cmpq $128, %rax
jne .L5
Because temp in main() is of type int (corresponding to %edx in the assembly), the value has to be converted back and forth between float and int in the loop. According to http://www.agner.org/optimize/instruction_tables.pdf, CVTSI2SS and CVT(T)SS2SI each have 6 cycles latency on Skylake. Furthermore, the conversions are in the dependency-chain, so out-of-order and superscalar execution do not help much in this case.
Changing main()s int temp to float temp removes these conversions.

In x86, why do I have the same instruction two times, with reversed operands?

I am doing several experiments with x86 asm trying to see how common language constructs map into assembly. In my current experiment, I am trying to see specifically how C language pointers map to register-indirect addressing. I have written a fairly hello-world like pointer program:
#include <stdio.h>
int
main (void)
{
int value = 5;
int *int_val = &value;
printf ("The value we have is %d\n", *int_val);
return 0;
}
and compiled it to the following asm using: gcc -o pointer.s -fno-asynchronous-unwind-tables pointer.c:[1][2]
.file "pointer.c"
.section .rodata
.LC0:
.string "The value we have is %d\n"
.text
.globl main
.type main, #function
main:
;------- function prologue
pushq %rbp
movq %rsp, %rbp
;---------------------------------
subq $32, %rsp
movq %fs:40, %rax
movq %rax, -8(%rbp)
xorl %eax, %eax
;----------------------------------
movl $5, -20(%rbp) ; This is where the value 5 is stored in `value` (automatic allocation)
;----------------------------------
leaq -20(%rbp), %rax ;; (GUESS) If I have understood correctly, this is where the address of `value` is
;; extracted, and stored into %rax
;----------------------------------
movq %rax, -16(%rbp) ;;
movq -16(%rbp), %rax ;; Why do I have two times the same instructions, with reversed operands???
;----------------------------------
movl (%rax), %eax
movl %eax, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
;----------------------------------
movl $0, %eax
movq -8(%rbp), %rdx
xorq %fs:40, %rdx
je .L3
call __stack_chk_fail
.L3:
leave
ret
.size main, .-main
.ident "GCC: (Ubuntu 4.9.1-16ubuntu6) 4.9.1"
.section .note.GNU-stack,"",#progbits
My issue is that I don't understand why it contains the instruction movq two times, with reversed operands. Could someone explain it to me?
[1]: I want to avoid having my asm code interspersed with cfi directives when I don't need them at all.
[2]: My environment is Ubuntu 14.10, gcc 4.9.1 (modified by ubuntu), and Gnu assembler (GNU Binutils for Ubuntu) 2.24.90.20141014, configured to target x86_64-linux-gnu
Maybe it will be clearer if you reorganize your blocks:
;----------------------------------
leaq -20(%rbp), %rax ; &value
movq %rax, -16(%rbp) ; int_val
;----------------------------------
movq -16(%rbp), %rax ; int_val
movl (%rax), %eax ; *int_val
movl %eax, %esi ; printf-argument
movl $.LC0, %edi ; printf-argument (format-string)
movl $0, %eax ; no floating-point numbers
call printf
;----------------------------------
The first block performs int *int_val = &value;, the second block performs printf .... Without optimization, the blocks are independent.
Since you're not doing any optimization, gcc creates very simple-minded code that does each statement in the program one at a time without looking at any other statement. So in your example, it stores a value into the variable int_val, and then the very next instruction reads that variable again as part of the next statement. In both cases, it is using %rax as the temporary to hold value, as that's the first register generally used for things.

Understanding simple assember program [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
I have practiced Assembler long time ago and I would like to understand a simple program (I generate assembler code from a C code) which adds 2 vectors (actually 2 arrays) and store the result in another vector (an output array). My goal is after to study vectorization. For this, I use gcc-4.9 under Debian Wheezy on i7-core processor.
Here the C code snippet (not vectorized version) :
#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;
}
I generate Assembler code with AT&T syntax :
gcc -std=c99 -c main_no_vectorized.c -O3 -S -o main_no_vectorized.s
Here is the assembly code :
.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
Could you explain to me the main steps of this above assembly code in relation with the C code, in particulary the "test" function, the loop of initialization in main function and the parameters passing (i.e where's the push and pop instructions for the stack) and the effective addition of "a" and "b" arrays ?
What corresponds to .L2, .L3, ... segments ? is there a relation with L2 cache, L3 cache ?
Sorry for these basics questions but I begin with Intel x86_64 assembler.
Thanks for your precious help
The generated assembly code is quite complicated. It first checks to see if the arrays a, b, and c overlap in a way that will cause an optimized loop to fail. For example, if you did this:
test(tab1, tab2, &tab1[1]);
then the overlap would be detected and cause the code to jump to L7 (the straightforward implementation). By the way, L stands for Label, and the label numbers are just generated by the compiler with no particular meaning. So L1, L2, L3, etc are just labels that are used for the code to branch to various places. The overlap checks start at .LFB3 and end at the last je .L7.
If no overlap is detected, then an optimized loop will be used. This optimized loop will try to add two doubles at a time instead of just one. The first thing the optimized loop does is to find out if array a is aligned to a 16 byte boundary (the testb $8, %dil instruction). If it is, it will jump to L8 to load a set of constants (e.g. r9 = 5000). If the array is not aligned, if will fall through and load a different set of constants (e.g. r9 = 4999), and also handle the first element. This is because the unaligned case will need to do 4999 iterations two at a time and handle the first and last unaligned elements separately outside the loop. The aligned case will just do 5000 iterations.
Either way, the code reaches L3 next. The code at L3 and L4 is the optimized loop that does the adds two at a time using the addpd instruction (the nonoptimized loop at L7 used addsd to do one add at a time). After the L4 loop finishes, it checks to see if it needs to handle the last element (for the unaligned case). Then it returns with the ret instruction.
By the way, it helps to know that when test is called, a is in rdi, b is in rsi, and c is in rdx. That is the calling convention for 64-bit. Therefore, there are no arguments pushed on the stack. If you don't understand x86 assembly too well, concentrate on the code starting at L7. That is the non-optimized version and you should be able to figure that part out given that I said your three arguments were in rdi, rsi, and rdx.
The .L2 and such are labels, they are used to refer to the next instruction. They are pretty much exactly like labels in C, if you've used goto. The primary use of a label is with a jump or branch, to specify where the jump goes to.
For example, the .L2 label is start of the body of your for (i = 0; i < SIZE; i++) loop in test(), it is counting by 8 bytes (the size of a double) up to 8*10000. The last instruction in the loop is jne .L2, which jumps to .L2 if the previous comparison was not equal.
You may find this reference (PDF) on x64 helpful.

Resources