Facilitating good code generation
This section contains hints on how to help the compiler generate good code.
Writing optimization-friendly source code
The following is a list of programming techniques that will, when followed, enable the compiler to better optimize the application.
Local variables—auto variables and parameters—are preferred over static or global variables. The reason is that the optimizer must assume, for example, that called functions can modify non-local variables. When the life spans for local variables end, the previously occupied memory can then be reused. Globally declared variables will occupy data memory during the whole program execution.
Avoid taking the address of local variables using the
&operator. This is inefficient for two main reasons. First, the variable must be placed in memory, and therefore cannot be placed in a processor register. This results in larger and slower code. Second, the optimizer can no longer assume that the local variable is unaffected over function calls.Module-local variables—variables that are declared static—are preferred over global variables (non-static). Also, avoid taking the address of frequently accessed static variables.
The compiler is capable of inlining functions, see Function inlining. To maximize the effect of the inlining transformation, it is good practice to place the definitions of small functions called from more than one module in the header file rather than in the implementation file. Alternatively, you can use multi-file compilation. For more information, see Multi-file compilation units.
Avoid using inline assembler without operands and clobbered resources. Instead, use SFRs or intrinsic functions if available. Otherwise, use inline assembler with operands and clobbered resources or write a separate module in assembler language. For more information, see Mixing C and assembler.
Set the heap size to a value which accommodates the needs of the standard I/O buffer, for example to 1 Kbyte.
Saving stack space and RAM memory
The following is a list of programming techniques that save memory and stack space:
Function prototypes
It is possible to declare and define functions using one of two different styles:
Prototyped
Kernighan & Ritchie C (K&R C)
Both styles are valid C, however it is strongly recommended to use the prototyped style, and provide a prototype declaration for each public function in a header that is included both in the compilation unit defining the function and in all compilation units using it.
The compiler will not perform type checking on parameters passed to functions declared using K&R style. Using prototype declarations will also result in more efficient code in some cases, as there is no need for type promotion for these functions.
To make the compiler require that all function definitions use the prototyped style, and that all public functions have been declared before being defined, use the Project>Options>C/C++ Compiler>Language 1>Require prototypes compiler option (‑‑require_prototypes).
Prototyped style
In prototyped function declarations, the type for each parameter must be specified.
int Test(char, int); /* Declaration */
int Test(char ch, int i) /* Definition */
{
return i + ch;
}Kernighan & Ritchie style
In K&R style—pre-Standard C—it is not possible to declare a function prototyped. Instead, an empty parameter list is used in the function declaration. Also, the definition looks different.
For example:
int Test(); /* Declaration */
int Test(ch, i) /* Definition */
char ch;
int i;
{
return i + ch;
}Integer types and bit negation
In some situations, the rules for integer types and their conversion lead to possibly confusing behavior. Things to look out for are assignments or conditionals (test expressions) involving types with different size, and logical operations, especially bit negation. Here, types also includes types of constants.
In some cases there might be warnings—for example, for constant conditional or pointless comparison—in others just a different result than what is expected. Under certain circumstances the compiler might warn only at higher optimizations, for example, if the compiler relies on optimizations to identify some instances of constant conditionals. In this example, an 8-bit character, a 32-bit integer, and two’s complement is assumed:
void F1(unsigned char c1)
{
if (c1 == ~0x80)
;
}Here, the test is always false. On the right hand side, 0x80 is 0x00000080, and ~0x00000080 becomes 0xFFFFFF7F. On the left hand side, c1 is an 8-bit unsigned character in the range 0–255, which can never be equal to 0xFFFFFF7F. Furthermore, it cannot be negative, which means that the integral promoted value can never have the topmost 8 bits set.
Protecting simultaneously accessed variables
Variables that are accessed asynchronously, for example, by interrupt routines or by code executing in separate threads, must be properly marked and have adequate protection. The only exception to this is a variable that is always read-only.
To mark a variable properly, use the volatile keyword. This informs the compiler, among other things, that the variable can be changed from other threads. The compiler will then avoid optimizing on the variable—for example, keeping track of the variable in registers—will not delay writes to it, and be careful accessing the variable only the number of times given in the source code.
For sequences of accesses to variables that you do not want to be interrupted, use the __monitor keyword. This must be done for both write and read sequences, otherwise you might end up reading a partially updated variable. Accessing a small-sized volatile variable can be an atomic operation, but you should not rely on it unless you continuously study the compiler output. It is safer to use the __monitor keyword to ensure that the sequence is an atomic operation. For more information, see __monitor.
For more information about the volatile type qualifier and the rules for accessing volatile objects, see Declaring objects volatile.
Accessing special function registers
Specific header files for several RH850 devices are included in the IAR product installation. The header files are named iodevice.h and define the processor-specific special function registers (SFRs).
Note
Each header file contains one section used by the compiler, and one section used by the assembler.
SFRs with bitfields are declared in the header file.
__no_init volatile union
{
unsigned short mwctl2;
struct
{
unsigned short edr : 1;
unsigned short edw : 1;
unsigned short lee : 2;
unsigned short lemd : 2;
unsigned short lepl : 2;
} mwctl2bit;
} @ 0xFFFF8000;
/* By including the appropriate include file in your code,
* it is possible to access either the whole register or any
* individual bit (or bitfields) from C code as follows.
*/
void Test()
{
/* Whole register access */
mwctl2 = 0x1234;
/* Bitfield accesses */
mwctl2bit.edw = 1;
mwctl2bit.lepl = 3;
}
You can also use the header files as templates when you create new header files for other RH850 devices. For information about the @ operator, see Controlling data and function placement in memory.
Passing values between C and assembler objects
The following example shows how you in your C source code can use inline assembler to set and get values from a special purpose register:
static unsigned long get_EIPSW(void)
{
static unsigned long value;
asm volatile("stsr EIPSW,%0" : : "=r"(value));
return value;
}
static void set_EIPSW(unsigned long value)
{
asm volatile("ldsr %0,EIPSW" : "r"(value));
}The general purpose register is used for getting and setting the value of the special purpose register EIPSW. The same method can also be used for accessing other special purpose registers and specific instructions.
To read more about inline assembler, see Inline assembler.
Non-initialized variables
Normally, the runtime environment will initialize all global and static variables when the application is started.
The compiler supports the declaration of variables that will not be initialized, using the __no_init type modifier. They can be specified either as a keyword or using the #pragmaobject_attribute directive. The compiler places such variables in a separate section, according to the specified memory keyword.
For __no_init, the const keyword implies that an object is read-only, rather than that the object is stored in read-only memory. It is not possible to give a __no_init object an initial value.
Variables declared using the __no_init keyword could, for example, be large input buffers or mapped to special RAM that keeps its content even when the application is turned off.
For more information, see __no_init.
Note
To use this keyword, language extensions must be enabled, see -e. For more information, see object_attribute.