11

The latest version of gcc is producing assembly that doesn't make sense to me. I compiled the code using no optimization; but, some parts of this code don't make sense, even with no optimization.

Here is the C source:

  #include <stdio.h>

   int main()
   {
     int a = 1324;
     int b = 5657;
     int difference = 9876;
     int printf_answer = 2221;

     difference = a - b;

     printf_answer = printf("%d + %d = %d\n", a, b, difference);

     return difference;
   }

It produces this assembly:

    .file   "exampleIML-1b.c"
    .section    .rodata
.LC0:
    .string "%d + %d = %d\n"
    .text
.globl main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    pushq   %rbx
    subq    $24, %rsp
    movl    $1324, -32(%rbp)
    movl    $5657, -28(%rbp)
    movl    $9876, -24(%rbp)
    movl    $2221, -20(%rbp)
    movl    -28(%rbp), %eax
    movl    -32(%rbp), %edx
    movl    %edx, %ecx
    subl    %eax, %ecx
    movl    %ecx, %eax
    movl    %eax, -24(%rbp)
    movl    $.LC0, %eax
    movl    -24(%rbp), %ecx
    movl    -28(%rbp), %edx
    movl    -32(%rbp), %ebx
    .cfi_offset 3, -24
    movl    %ebx, %esi
    movq    %rax, %rdi
    movl    $0, %eax
    call    printf
    movl    %eax, -20(%rbp)
    movl    -24(%rbp), %eax
    addq    $24, %rsp
    popq    %rbx
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
    .ident  "GCC: (GNU) 4.4.6 20120305 (Red Hat 4.4.6-4)"
    .section    .note.GNU-stack,"",@progbits

Several things don't make sense:

(1) Why are we pushing %rbx? What is in %rbx that needs to be saved?

(2) Why are we moving %edx to %ecx before subtracting? What doesn't it just do sub %eax, %edx?

(3) Similarly, why the move from %ecx back to %eax before storing the value?

(4) The compiler is putting the variable a in memory location -32(%rbp). Unless I'm adding wrong, isn't -32(%rbp) equal to the stack pointer? Shouldn't all local variables be stored at values less than the current stack pointer?

I'm using this version of gcc:

[eos17:~/Courses/CS451/IntelMachineLanguage]$ gcc -v Using built-in specs. 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-languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --disable-dssi --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-1.5.0.0/jre --enable-libgcj-multifile --enable-java-maintainer-mode --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --disable-libjava-multilib --with-ppl --with-cloog --with-tune=generic --with-arch_32=i686 --build=x86_64-redhat-linux Thread model: posix gcc version 4.4.6 20120305 (Red Hat 4.4.6-4) (GCC)

Zack
  • 6,232
  • 8
  • 38
  • 68
  • 3
    You are asking: I did compile with optimization off. Why did gcc produce unoptimized code? -1. – Gunther Piez Oct 04 '12 at 23:24
  • 4
    I disagree. Things like storing local variables in memory when they all fit in registers is unoptimized. It makes sense why a compiler looking at code line-by-line would write assembly that way. In contrast, I don't see why a compilier, even in an unoptimized mode, perform a move before and after the subtract? – Zack Oct 05 '12 at 02:02
  • 4
    No, that's just your personal interpretation of "unoptimized". For gcc it's just the simplest way of generating code - always using the same registers for operands completely avoids any need for a register allocator, even if it introduces unnecessary moves. The same goes for the `push/pop rbx` - when optimizing, gcc is actually smart enough to recognize that preserving registers through main is not needed – Gunther Piez Oct 05 '12 at 11:19

2 Answers2

21
GCC dictates how the stack is used. Contract between caller and callee on x86:

    * after call instruction:
          o %eip points at first instruction of function
          o %esp+4 points at first argument
          o %esp points at return address 
    * after ret instruction:
          o %eip contains return address
          o %esp points at arguments pushed by caller
          o called function may have trashed arguments
          o %eax contains return value (or trash if function is void)
          o %ecx, %edx may be trashed
          o %ebp, %ebx, %esi, %edi must contain contents from time of call 
    * Terminology:
          o %eax, %ecx, %edx are "caller save" registers
          o %ebp, %ebx, %esi, %edi are "callee save" registers

The main function is like any other function in this context. gcc decided to use ebx for intermediate calculations, so it preserves its value.

Serge
  • 6,088
  • 17
  • 27
  • Thanks. I should have seen the use of ebx. – Zack Oct 05 '12 at 02:03
  • Specifically, I should have noticed that the compiler used %ebx when setting up the parameters to the call for printf. – Zack Oct 05 '12 at 02:11
  • The interesting thing is that gcc is loading the variable from memory into %ebx, then moving it to %esi. Previous versions of gcc (i.e., the one we used last semester) loaded the data directly from memory into %esi (even when not optimizing). – Zack Oct 05 '12 at 02:14
  • Yes. As you compiled it with no optimization, the code is really not efficient. So, probably the '-O2' code would be much readable – Serge Oct 05 '12 at 02:14
  • The code with -O2 is actually optimized too much for the purpose of the assignment. With -O2, the compiler does the subtraction and initializes the variable with the result (i.e., no sub instruction), and it keeps all the variables in registers (i.e., no memory accesses). – Zack Oct 05 '12 at 14:23
5

By default gcc compiles with optimization disabled, which is the case here, apparently.

You need to enable it with one of the optimization switches (e.g. -O2 or -O3).

Then you will not see redundant and seemingly meaningless things.

As for rbx, it has to be preserved because that's what the calling conventions require. Your function modifies it (movl -32(%rbp), %ebx), so it has to be saved and restored explicitly.

Alexey Frunze
  • 61,140
  • 12
  • 83
  • 180