GNU GCC inline assembly can do mere assignments via pseudo-empty asm()
statements, like:
asm("" : "=r"(foo) : "0"(tick));
This tells the compiler:
- The variable
foo
is to be taken from a register after the inline assembly block - The variable
tick
is to be passed in - in the same register (argument zero)
The actual choice of which register is to be used is completely left to the compiler.
The trick here are the output and input constraints - we just alias the (one and only) input to the output, and the compiler will, on its own, choose a suitable register, and generate the instructions necessary to load / store the respective variables before / after the "actual" inline assembly code. You could even do:
asm("" : "=r"(foo1), "=r"(foo2) : "0"(tick1) , "1"(tick2));
to do two "assignments" in a single inline assembly statement.
This compiler-generated "set the inputs, retrieve the outputs" code generation happens even if the actual inline assembly is empty (as here).
Another example: Say you want to read the current program counter - the PC
register. You can do that on ARM via two different inline assembly statements:
asm("" : "=pc"(foo));
asm("mov %0, PC" : "=r"(foo));
This is not 100% identical; in the second case, the compiler knows that whatever register it wants to see foo
in after the asm
, it'll find it there. In the former, the compiler knows that were it to use foo
after the statement, it needs to retrieve it from PC
. The difference between the two would be if you did:
uintptr_t *val;
uintptr_t foo;
asm("" : "=pc"(foo));
*val = foo;
In this case, the compiler can possibly identify that this can be turned into a single str [R...], PC
because it knows foo
is in pc
after the asm. Were you to write this via
asm("mov %0, PC" : "=r"(foo));
*val = foo;
the compiler would be forced to create (assuming it chooses R0
/ R1
for foo
/val
):
MOV R0, PC
STR [R1], R0
The documentation for this behaviour is largely in the "Extended ASM" section of the GCC manuals, see the example for the contrived combine
instruction.