Skip to main content

IAR Embedded Workbench for RX 5.20

Facilitating good code generation

In this section:

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.

Saving stack space and RAM memory

The following is a list of programming techniques that save memory and stack space:

  • If stack space is limited, avoid long call chains and recursive functions.

  • Avoid using large non-scalar types, such as structures, as parameters or return type. To save stack space, you should instead pass them as pointers or, in C++, as references.

Aligning the function entry point

The runtime performance of a function depends on the entry address assigned by the linker. To make the function execution time less dependent on the entry address, the alignment of the function entry point can be specified explicitly using a compiler option, see --align_func. A higher alignment does not necessarily make the function faster, but the execution time will be more predictable.

Register locking

Register locking means that the compiler can be instructed never to touch some processor registers. This can be useful in several situations. For example:

  • Some parts of a system could be written in assembler language to improve execution speed. These parts could be given dedicated processor registers.

  • The register could be used by an operating system, or by other third-party software.

Registers are locked using the --lock compiler option. See --lock.

In general, if two modules are used together in the same application, they should have the same registers locked. The reason is that registers that can be locked could also be used as parameter registers when calling functions. In other words, the calling convention will depend on which registers that are locked.

To ensure that you only link modules with the same registers locked, you can use the __lockRn runtime model attribute; see Checking module consistency.

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 RX 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. This example is from ior5f56108.h:

__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;
} @ 8;

/* 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 RX 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_INTB(void)
{
  unsigned long value;
  asm("mvfc INTB,%0 ;hej" : "=r"(value));
  return value;
}

static void set_INTB(unsigned long value)
{
  asm("mvtc %0,INTB" : : "r"(value));
}

The general purpose register is used for getting and setting the value of the special purpose register INTB. The same method can also be used for accessing other special purpose registers and specific instructions.

To read more about inline assembler, see .

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.