C/C++ Development Suite
Login
Introduction
Summary of changes from 5.54 to 5.55
Summary of changes up to 5.60
Summary of changes from 5.61 to 5.64
Halfword memory access
Packed structs
Inline assembler
C99 pragmas
Constant data
Global data controls
New/changed options

Downloads
Downloads Year 2

   

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.

© 2003/2006 Castle Technology Ltd 32-bit RISC OS