Mixing C and assembler
The IAR C/C++ Compiler for Arm provides several ways to access low-level resources:
Modules written entirely in assembler
Intrinsic functions (the C alternative)
Inline assembler.
It might be tempting to use simple inline assembler. However, you should carefully choose which method to use.
Intrinsic functions
The compiler provides a few predefined functions that allow direct access to low-level processor operations without having to use the assembler language. These functions are known as intrinsic functions. They can be useful in, for example, time-critical routines.
An intrinsic function looks like a normal function call, but it is really a built-in function that the compiler recognizes. The intrinsic functions compile into inline code, either as a single instruction, or as a short sequence of instructions.
For more information about the available intrinsic functions, see Intrinsic functions.
Mixing C and assembler modules
It is possible to write parts of your application in assembler and mix them with your C or C++ modules.
This causes some overhead in the form of function call and return instruction sequences, and the compiler will regard some registers as scratch registers. In many cases, the overhead of the extra instructions can be removed by the optimizer.
An important advantage is that you will have a well-defined interface between what the compiler produces and what you write in assembler. When using inline assembler, you will not have any guarantees that your inline assembler lines do not interfere with the compiler generated code.
When an application is written partly in assembler language and partly in C or C++, you are faced with several questions:
How should the assembler code be written so that it can be called from C?
Where does the assembler code find its parameters, and how is the return value passed back to the caller?
How should assembler code call functions written in C?
How are global C variables accessed from code written in assembler language?
Why does not the debugger display the call stack when assembler code is being debugged?
The first question is discussed in Calling assembler routines from C. The following two are covered in Calling convention.
The answer to the final question is that the call stack can be displayed when you run assembler code in the debugger. However, the debugger requires information about the call frame, which must be supplied as annotations in the assembler source file. For more information, see Call frame information.
The recommended method for mixing C or C++ and assembler modules is described in Calling assembler routines from C, and Calling assembler routines from C++, respectively.
Inline assembler
Inline assembler can be used for inserting assembler instructions directly into a C or C++ function. Typically, this can be useful if you need to:
Access hardware resources that are not accessible in C (in other words, when there is no definition for an SFR or there is no suitable intrinsic function available).
Manually write a time-critical sequence of code that if written in C will not have the right timing.
Manually write a speed-critical sequence of code that if written in C will be too slow.
An inline assembler statement is similar to a C function in that it can take input arguments (input operands), have return values (output operands), and read or write to C symbols (via the operands). An inline assembler statement can also declare clobbered resources, that is, values in registers and memory that have been overwritten.
Limitations
Most things you can to do in normal assembler language are also possible with inline assembler, with the following differences:
Alignment cannot be controlled—this means, for example, that
DC32directives might be misaligned.The only accepted register synonyms in 32-bit mode are
SP(forR13),LR(forR14), andPC(forR15).The only accepted register synonyms in 64-bit mode are
IP0(forX16),IP1(forX17),FP(forX29), andLR(forX30).In general, assembler directives will cause errors or have no meaning. However, data definition directives will work as expected.
Resources used (registers, memory, etc) that are also used by the C compiler must be declared as operands or clobbered resources.
If you do not want to risk that the inline assembler statement is optimized away by the compiler, you must declare it
volatile.Accessing a C symbol or using a constant expression requires the use of operands.
Dependencies between the expressions for the operands might result in an error.
The pseudo-instruction
LDR Rd, =expris not available from inline assembler.
Risks with inline assembler
Without operands and clobbered resources, inline assembler statements have no interface with the surrounding C source code. This makes the inline assembler code fragile, and might also become a maintenance problem if you update the compiler in the future. There are also several limitations to using inline assembler without operands and clobbered resources:
The compiler’s various optimizations will disregard any effects of the inline statements, which will not be optimized at all.
Inlining of functions with assembler statements without declared side-effects will not be done.
The inline assembler statement will be
volatileand clobbered memory is not implied. This means that the compiler will not remove the assembler statement. It will simply be inserted at the given location in the program flow. The consequences or side-effects that the insertion might have on the surrounding code are not taken into consideration. If, for example, registers or memory locations are altered, they might have to be restored within the sequence of inline assembler instructions for the rest of the code to work properly.
Warning
The following example—for Arm mode—demonstrates the risks of using the asm keyword without operands and clobbers:
int Add(int term1, int term2)
{
asm("adds r0,r0,r1");
return term1;
}In this example:
The function
Addassumes that values are passed and returned in registers in a way that they might not always be, for example, if the function is inlined.The
sin theaddsinstruction implies that the condition flags are updated, which you specify using theccclobber operand. Otherwise, the compiler will assume that the condition flags are not modified.
Inline assembler without using operands or clobbered resources is therefore often best avoided. The compiler will issue a remark for them.
An example of how to use clobbered memory
int StoreExclusive(unsigned long * location, unsigned long value)
{
int failed;
asm("strex %0,%2,[%1]"
: "=&r"(failed)
: "r"(location), "r"(value)
: "memory");
/* Note: 'strex' requires Armv6 (Arm) or Armv6T2 (THUMB) */
return failed;
}