| |
|
Inline assembler
The inline assembler is a powerful new feature allowing the programmer to
easily access the full functionality of the ARM instruction set within the C
environment.
The assembler uses the same syntax as that in the ARM ADS. See the Mixing C,
C++ and Assembly Language chapter of the ADS 1.2 Developer Guide (available
from ARM's web site) for more detailed documentation and further examples.
Inline assembler is invoked by an __asm statement:
__asm
{
...
}
Assembler instructions are separated by newlines or semicolons. Within
the assembler block, C comments and macros work as normal.
The assembler is not a "raw" assembler. The instructions within the __asm
block are merged into the platform-independent pseudo-instructions generated
by the surrounding C code, and are then subject to all the following compiler
optimisations (eg common subexpression elimination, dead code analysis,
register allocation and peepholing) before being converted back into ARM
code. The resulting object code is not guaranteed to be identical to the
source assembly - think of it as an optimising assembler.
Within the assembler, physical ARM register names can be used, but these are
not automatically bound to any C variables - it is not valid to, say, access
R0 in an attempt to inspect the first argument of your function. Instead, C
expressions can be used in the place of registers. Physical registers only
have scope within the assembler, and their use will constrain the compiler's
register allocation - avoid where possible.
For example:
char *my_strcpy(char *dst, const char *src)
{
char *orig_src;
int c;
__asm
{
MOV orig_src, src
loop:
LDRB c, [src], #1
STRB c, [dst], #1
TEQ c, #0
BNE loop
}
return orig_src;
}
// returns old I bit
inline int disable_interrupts(void)
{
int old_i, temp;
__asm
{
MRS temp, CPSR
AND old_i, temp, #0x80
ORR temp, temp, #0x80
MSR CPSR_c, temp
}
return old_i;
}
Note the powerful conjunction of inline and __asm in the last example. Also,
because of the optimiser, if you don't use the return value of the function
when it is inlined, the unneeded AND instruction will be eliminated by the
compiler.
If you do use physical registers, you must explicitly transfer them to and
from C variables:
size_t my_strlen(const char *s)
{
size_t len;
__asm
{
// the following instruction must be here to logically
// transfer s into physical register a1. In the final
// output, no MOV will actually be generated as s is
// already in a1.
MOV a1, s
MOV a2, #0
loop:
LDRB a3, [a1],#1
TEQ a3, #0
ADDNE a2, a2, #1
BNE loop
// a2 must be transferred into a C variable so we can
// return it.
MOV len, a2
}
return len;
}
Because (virtually) arbitrary expressions can be used, inline assembly
can be considerably more expressive than normal assembly. For example:
SWP test,0,[&semaphore]
MOV a[x][y][z], #0
MUL x,x,#31
MOV y,1000000/x
MOV R0,"Hello"
The full ARM instruction set is supported, with the following notes and
exceptions:
- B operates like a C goto - the target is a label. Labels can be
placed in assembly the same as in C.
- BL and SWI must specify the physical registers they use. This is
done by specifying input, output and corrupted register lists as part
of the instruction:
MOV R0, #0x124
MOV R1, sprite_area
MOV R2, sprite_name
SWI OS_SpriteOp, {R0-R2}, {R2}, {LR,PSR}
MOV sprite_address, R2
For best practice, BL and SWI are the only reason you should use
physical registers. Any or all of the lists can be omitted. If
they are omitted, BL and SWI calls are assumed to have no input
or output registers, and corrupt R0-R3 and R12, plus LR for BL.
Note that the inability to manipulate the stack rules out the
ability to call APCS functions with more than 4 words of arguments
using BL.
To call a normal C function, it is usually better to switch back to
C than to use BL. Alternatively, use an expression:
ADD len, strlen(fred), #1
This removes any dependencies on the calling standard.
- No other instructions that change the program counter can be used.
BX and BLX are not supported.
- The stack cannot be used. Use C variables for storage, and if you
do use physical registers, the compiler will preserve them
around your code if necessary.
- FPA instructions (and hence registers) are not supported.
- Because the inline assembler doesn't support the FPA, BL and SWI
calls must not corrupt FPA registers.
|