GCC's inline assembler recognizes the declarators =r
and =&r
. These make sense to me: the =r
lets the assembler reuse an input register for output.
However, GCC's inline assembler also recognizes the declarators +r
and +&r
. These make less sense to me. After all, isn't the distinction between +r
and +&r
a distinction without a difference? Does the +r
alone not suffice to tell the compiler to reserve a register for the sole use of a single variable?
For example, what is wrong with the following GCC code?
#include <stdio.h>
int main()
{
int a = 0;
printf("Initially, a == %d.\n", a);
/* The architecture is amd64/x86-64. */
asm(
"inc %[a]\n"
: [a] "+r" (a)
: : "cc"
);
printf("However, after incrementation, a == %d.\n", a);
return 0;
}
Notice incidentally that my inline assembly lacks an input declaration because, in my (perhaps mistaken) mind, the +r
covers input, clobbering, output, everything. What have I misunderstood, please?
BACKGROUND
I have programmed 8- and 16-bit microcontrollers in assembly a bit, but have little or no experience at coding assembly in a hosted environment.
GCC assumes by default that inline assembly statements consist of a simple single assembly instruction that consumes all of its input operands before writing to any of its output operands. When writing inline assembly statements that use multiple assembly instructions this assumption is often broken, so the early clobber constraint modifier &
needs to be used to indicate which output operands are written to before all the input operands are consumed. This is necessary with both output operands that use the =
modifier and read/write output operands that use +
. For example consider the two following functions:
int
foo() {
int a = 1;
asm("add %1, %0" : "+r" (a) : "r" (1));
return a;
}
int
bar() {
int a = 1;
asm("add %1, %0\n\t"
"add %1, %0"
: "+r" (a) : "r" (1));
return a;
}
Both inline assembly statements use the same operands and the same constraints, but the only the inline assembly statement in foo
is correct, the one in bar
is broken. With optimizations enabled GCC generates the following code for the two functions:
_foo:
movl $1, %eax
/APP
add %eax, %eax
/NO_APP
ret
_bar:
movl $1, %eax
/APP
add %eax, %eax
add %eax, %eax
/NO_APP
ret
GCC sees no reason not to use the same register, EAX, for both operands in both inline assembly statements. While this isn't a problem in foo
, it causes bar
to calculate the wrong result of 4 instead of the expected 3.
A correct version of bar
would use the early clobber modifier:
int
baz() {
int a = 1;
asm("add %1, %0\n\t"
"add %1, %0"
: "+&r" (a) : "r" (1));
return a;
}
_baz:
movl $1, %eax
movl %eax, %edx
/APP
add %edx, %eax
add %edx, %eax
/NO_APP
ret
When compiling baz
GCC knows to use a different register for both operands so it doesn't matter that the read/write output operand is modified before the input operand is read for the second time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With