This is something that’s been bugging me for a while here, and I might as well write it down since I finally found a solution.
I have an atomic-increment function. To make it actually atomic, it uses assembly. Here’s the PPC version:
static inline int atomic_inc(int * operand)
{
int retval;
register unsigned int incrd = incrd; // silence initialization complaints
asm volatile ("1:\n\t"
"lwarx %0,0,%1\n\t" /* reserve operand into retval */
"addi %2,%0,1\n\t" /* increment */
"stwcx. %2,0,%1\n\t" /* un-reserve operand */
"bne- 1b\n\t" /* if it failed, try again */
"isync" /* make sure it wasn't all just a dream */
:"=&r" (retval)
:"r" (operand), "r" (incrd)
:"cc","memory");
return retval;
}
Now, what exactly is wrong with that, eh? This works great on Linux. The general GCC compiles this just fine, as does the PGI compiler, IBM’s compiler, and Intel’s compiler.
Apple’s compiler? Here’s the error I get:
gcc -c test.c
/var/tmp/ccqu2RmV.s:5949:Parameter error: r0 not allowed for parameter 2 (code as 0 not r0)
Okay, so, some kind of monkey business is going on. What does this look like in the .S file?
1:
lwarx r0,0,r2
addi r3,r0,1
stwcx. r3,0,r2
bne- 1b
isync
mr r3,r0
It decided (retval) was going to be r0! Even though that’s apparently not allowed! (FYI it’s the addi
that generates the error).
The correct workaround is to use the barely documented “b” option, like this:
static inline int atomic_inc(int * operand)
{
int retval;
register unsigned int incrd = incrd; // silence initialization complaints
asm volatile ("1:\n\t"
"lwarx %0,0,%1\n\t" /* reserve operand into retval */
"addi %2,%0,1\n\t" /* increment */
"stwcx. %2,0,%1\n\t" /* un-reserve operand */
"bne- 1b\n\t" /* if it failed, try again */
"isync" /* make sure it wasn't all just a dream */
:"=&b" (retval) /* note the b instead of the r */
:"r" (operand), "r" (incrd)
:"cc","memory");
return retval;
}
That ensures, on PPC machines, that the value is a “base” register (aka not r0).
How gcc on Linux gets it right all the time, I have no idea. But it does.