Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding two numbers

I am trying to familiarise myself with x86 assembly using GCC's inline assembler. I'm trying to add two numbers (a and b) and store the result in c. I have four slightly different attempts, three of which work; the last doesn't produce the expected result.

The first two examples use an intermediate register, and these both work fine. The third and fourth examples try to add the two values directly without the intermediate register, but the results vary depending on the optimization level and the order in which I add the input values. What am I getting wrong?

Environment is:

i686-apple-darwin10-gcc-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5666) (dot 3)

First, the variables are declared as follows:

int a = 4;
int b = 7;
int c;

Example 1:

asm("   movl    %1,%%eax;"
    "   addl    %2,%%eax;"
    "   movl    %%eax,%0;"
    : "=r" (c)
    : "r" (a), "r" (b)
    : "%eax"
   );
printf("a=%d, b=%d, c=%d\n", a, b, c);
// output: a=4, b=7, c=11

Example 2:

asm("   movl    %2,%%eax;"
    "   addl    %1,%%eax;"
    "   movl    %%eax,%0;"
    : "=r" (c)
    : "r" (a), "r" (b)
    : "%eax"
   );
printf("a=%d, b=%d, c=%d\n", a, b, c);
// output: a=4, b=7, c=11

Example 3:

asm("   movl    %2,%0;"
    "   addl    %1,%0;"
    : "=r" (c)
    : "r" (a), "r" (b)
   );
printf("a=%d, b=%d, c=%d\n", a, b, c);
// output with -O0: a=4, b=7, c=11
// output with -O3: a=4, b=7, c=14

Example 4:

// this one appears to calculate a+a instead of a+b
asm("   movl    %1,%0;"
    "   addl    %2,%0;"
    : "=r" (c)
    : "r" (a), "r" (b)
   );
printf("a=%d, b=%d, c=%d\n", a, b, c);
// output with -O0: a=4, b=7, c=8
// output with -O3: a=4, b=7, c=11

SOLVED. Matthew Slattery's answer is correct. Before, it was trying to reuse eax for both b and c:

movl    -4(%rbp), %edx
movl    -8(%rbp), %eax
movl    %edx, %eax
addl    %eax, %eax

With Matthew's suggested fix in place, it now uses ecx to hold c separately.

movl    -4(%rbp), %edx
movl    -8(%rbp), %eax
movl    %edx, %ecx
addl    %eax, %ecx
like image 386
Graham Borland Avatar asked Dec 12 '11 12:12

Graham Borland


1 Answers

By default, gcc will assume that an inline asm block will finish using the input operands before updating the output operands. This means that both an input and an output may be assigned to the same register.

But, that isn't necessarily the case in your examples 3 and 4.

e.g. in example 3:

asm("   movl    %2,%0;"
    "   addl    %1,%0;"
    : "=r" (c)
    : "r" (a), "r" (b)
   );

...you have updated c (%0) before reading a (%1). If gcc happens to assign the same register to both %0 and %1, then it will calculate c = b; c += c, and hence will fail in exactly the way you observe:

printf("a=%d, b=%d, c=%d\n", a, b, c);
// output with -O0: a=4, b=7, c=11
// output with -O3: a=4, b=7, c=14

You can fix it by telling gcc that the output operand may be used before the inputs are consumed, by adding the "&" modifier to the operand, like this:

asm("   movl    %2,%0;"
    "   addl    %1,%0;"
    : "=&r" (c)
    : "r" (a), "r" (b)
   );

(See "Constraint Modifier Characters" in the gcc docs.)

like image 112
Matthew Slattery Avatar answered Oct 22 '22 07:10

Matthew Slattery