Calling convention
A calling convention is the way a function in a program calls another function. The compiler handles this automatically, but, if a function is written in assembler language, you must know where and how its parameters can be found, how to return to the program location from where it was called, and how to return the resulting value.
It is also important to know which registers an assembler-level routine must preserve. If the program preserves too many registers, the program might be inefficient. If it preserves too few registers, the result would be an incorrect program.
This section describes the calling convention used by the compiler. These items are examined:
At the end of the section, some examples are shown to describe the calling convention in practice.
Note
The calling convention complies with the RX ABI standard.
Function declarations
In C, a function must be declared in order for the compiler to know how to call it. A declaration could look as follows:
int MyFunction(int first, char * second);This means that the function takes two parameters: an integer and a pointer to a character. The function returns a value, an integer.
In the general case, this is the only knowledge that the compiler has about a function. Therefore, it must be able to deduce the calling convention from this information.
Using C linkage in C++ source code
In C++, a function can have either C or C++ linkage. To call assembler routines from C++, it is easiest if you make the C++ function have C linkage.
This is an example of a declaration of a function with C linkage:
extern "C"
{
int F(int);
}It is often practical to share header files between C and C++. This is an example of a declaration that declares a function with C linkage in both C and C++:
#ifdef __cplusplus
extern "C"
{
#endif
int F(int);
#ifdef __cplusplus
}
#endifPreserved versus scratch registers
The general RX CPU registers are divided into three separate sets, which are described in this section.
Scratch registers
Any function is permitted to destroy the contents of a scratch register. If a function needs the register value after a call to another function, it must store it during the call, for example, on the stack.
Any of the registers R1–R5 or R14–R15 can be used as a scratch register by the function.
Preserved registers
Preserved registers, on the other hand, are preserved across function calls. The called function can use the register for other purposes, but must save the value before using the register and restore it at the exit of the function.
The registers R6–R13 are preserved registers.
Special registers
The stack pointer register (R0) must at all times point to or below the last element on the stack. In the eventuality of an interrupt, everything below the point the stack pointer points to, will be destroyed.
The register R14 is used by veneers to extend the range of calls, so it can be destroyed between the calling point and the entry point of the called function.
Function entrance
It is much more efficient to use registers than to take a detour via memory, so the calling convention is designed to use registers as much as possible. Only a limited number of registers can be used for passing parameters—when no more registers are available, the remaining parameters are passed on the stack. The parameters are also passed on the stack in these cases:
Aggregate types (structures, unions and arrays) larger than 16 bytes, or with a lower alignment than 4
Unnamed parameters to variable length (variadic) functions; in other words, functions declared as
foo(param1,...), for exampleprintf.
Note
Interrupt functions cannot take any parameters.
Register parameters
The registers available for passing parameters are R1–R4:
Parameters | Passed in registers |
|---|---|
8- to 32-bit values |
|
64-bit values |
|
Aggregate values |
|
Small aggregate types are only passed in registers R1–R4 if they:
are 16 bytes or smaller
have an alignment of 4 or more.
Aggregate types that do not fit these two requirements will use a hidden parameter.
The assignment of registers to parameters is a straightforward process. Traversing the parameters in strict order from left to right, the first parameter is assigned to the available register or registers. Should there be no suitable register available, the parameter is passed on the stack. This process continues until there are no more parameter registers available or until all parameters have been passed.
Stack parameters and layout
Stack parameters are stored in the main memory, starting at the location pointed to by the stack pointer. Below the stack pointer (toward low memory) there is free space that the called function can use. The first stack parameter is stored at the location pointed to by the stack pointer. The next one is stored at the next location on the stack that is divisible by four, etc.
This figure illustrates how parameters are stored on the stack:

Objects on the stack should be aligned to 4 bytes at function entry, regardless of their size.
When passed in registers, aggregate types follow the setting of the byte order option --endian, but scalar types are always little-endian. On the stack, all parameters are stored according to the byte order setting.
Function exit
A function can return a value to the function or program that called it, or it can have the return type void.
The return value of a function, if any, can be scalar (such as integers and pointers), floating-point, or a structure.
Registers used for returning values
The registers available for returning values are:
Return values | Passed in registers |
|---|---|
8- and 16-bit scalars |
|
32-bit values |
|
64-bit values |
|
Aggregate values |
|
Stack layout at function exit
It is the responsibility of the caller to clean the stack after the called function has returned.
Return address handling
A function written in assembler language should, when finished, return to the caller. At a function call, the return address is stored on the stack.
Typically, a function returns by using the RTS or RTSD instruction.
Restrictions for special function types
Interrupt functions save all used registers. Task functions save no registers at all, and monitor functions save the interrupt status.
An interrupt function returns by using the RTE instruction. Task functions and monitor functions return by using the RTS or RTSD instruction, depending on whether they need to deallocate a stack frame or not.
Examples
The following section shows a series of declaration examples and the corresponding calling conventions. The complexity of the examples increases toward the end.
Example 1
Assume this function declaration:
int add1(int);
This function takes one parameter in the register R1, and the return value is passed back to its caller in the register R1.
This assembler routine is compatible with the declaration; it will return a value that is one number higher than the value of its parameter:
name return
section .text:CODE
code
add #1,R1
rts
endExample 2
This example shows how structures are passed on the stack. Assume these declarations:
struct MyStruct
{
short a;
short b;
short c;
short d;
short e;
};
int MyFunction(struct MyStruct x, int y);The calling function must reserve 20 bytes on the top of the stack and copy the contents of the struct to that location. The integer parameter y is passed in the register R1. The return value is passed back to its caller in the register R1.
Example 3
The function below will return a structure of type struct MyStruct.
struct MyStruct
{
int mA;
int mB;
};
struct MyStruct MyFunction(int x);In this case, the struct is small enough to fit in registers, so it is returned in R2R1.
Assume that the function instead was declared to return a pointer to the structure:
struct MyStruct *MyFunction(int x);
In this case, the return value is a scalar, so there is no hidden parameter. The parameter x is passed in R1, and the return value is returned in R1.