4.11.2.2 Clobber Operand
The list of clobbered registers is optional; however, if the instruction modifies registers that are not specified as operands, you need to inform the compiler of these changes.
Typically you can arrange the assembly so that you do not need to specify what has been clobbered. Indicating that a register has been clobbered will force the compiler to store their values before and reload them after your assembly instructions and will limit the ability of the compiler to optimize your code.
The following example will perform an atomic increment. It disables the interrupts then increments an 8-bit value pointed to by a pointer variable. Note, that a pointer is used because the incremented value needs to be stored before the interrupts are enabled.
asm volatile(
"cli" "\n\t"
"ld r24, %a0" "\n\t"
"inc r24" "\n\t"
"st %a0, r24" "\n\t"
"sei" "\n\t"
:
: "e" (ptr)
: "r24"
);
The compiler might produce the following code for the above:
cli
ld r24, Z
inc r24
st Z, r24
sei
To have this sequence avoid clobbering register r24, make use of the
special temporary register __tmp_reg__
defined by the compiler.
asm volatile(
"cli" "\n\t"
"ld __tmp_reg__, %a0" "\n\t"
"inc __tmp_reg__" "\n\t"
"st %a0, __tmp_reg__" "\n\t"
"sei" "\n\t"
:
: "e" (ptr)
);
The compiler will always reload the temporary register when it is needed.
The above code unconditionally re-enables the interrupts, which may not be desirable. To make the code more versatile, the current status can be stored in a register selected by the compiler.
{
uint8_t s;
asm volatile(
"in %0, __SREG__" "\n\t"
"cli" "\n\t"
"ld __tmp_reg__, %a1" "\n\t"
"inc __tmp_reg__" "\n\t"
"st %a1, __tmp_reg__" "\n\t"
"out __SREG__, %0" "\n\t"
: "=&r" (s)
: "e" (ptr)
);
}
The assembler code here modifies the variable, that ptr
points to, so the definition of ptr
should indicate that its target can
change unexpected, using the volatile
specifier, for example:
volatile uint8_t *ptr;
The special clobber memory informs the compiler that the assembler code may modify any memory location. It forces the compiler to update all variables for which the contents are currently held in a register before executing the assembler code.
When you use a memory clobber with an assembly instruction, it ensures
that all prior accesses to volatile
objects are complete before the
instruction executes, and that execution of volatile
accesses after the
instruction have not commenced. However, it does not prevent the compiler from moving
non-volatile
-related instructions across the barrier created by the
memory clobber instruction, as such instructions might be those that enable or disable
interrupts.