songhir
songhir

Reputation: 3583

Strange behavior with gcc inline assembly?

I'm new to gcc inline assembly. Why this code output "1" instead of "5"?

code:

#include <stdio.h>
static inline int atomic_add(volatile int *mem, int add)
{
    asm volatile(
            "lock xadd %0, (%1);"
            : "=a"(add)
            : "r"(mem), "a"(add)
            : "memory"
    );
    return add;
}

int main(void)
{
    int a=1;
    int b=5;
    printf ( "%d\n", atomic_add(&a, b) );
    return 0;
}

run:

$ ./a.out
1  # why not 5?

many thx. :)

Upvotes: 1

Views: 397

Answers (2)

Michael Burr
Michael Burr

Reputation: 340168

The variable add stars out with the value 5 and *mem starts out with 1.

The lock xadd %0, (%1) assembly template gets compiled by gcc to:

lock xadd %eax, (%edx)

GCC has to use eax because your constraints indicate that %0 should use %eax. Your constraint also ties %eax to the variable add. I believe that GCC is free to us whatever register it wants for the other operand (in my test it happened to use %edx).

So:

  • %eax starts with 5, and %edx points to a memory location that has the value 1
  • the xadd instruction swaps the two operands and places the sum in the destination, so after executing %eax has 1 and the memory pointed to by%edxcontains6`

    Your constraint also indicates that %eax should be stored back into the variable add, so add gets 1. And that is what is returned from the function.

Upvotes: 3

scottt
scottt

Reputation: 7228

In x86, XADD is the Exchange and Add instruction. So the register holding the add parameter became 1 after the lock xadd instruction. add is then returned by atomic_add() thus you see 1 printed instead of 5.

For atomic_add() you probably want to just use lock add instead of lock xadd:

#include <stdio.h>
static inline int atomic_add(volatile int *mem, int add)
{
    asm volatile(
            "lock add %0, (%1);"
            : "=a"(add)
            : "r"(mem), "a"(add)
            : "memory"
    );
    return add;
}

int main(void)
{
    int a=1;
    int b=5;
    printf ( "%d\n", atomic_add(&a, b) );
    return 0;
}

And this prints 5 like you expect:

$ ./a.out
5

Upvotes: 2

Related Questions