Skip to main content

IAR Embedded Workbench for RISC-V 3.40

Calling convention

In this section:

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.

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
}
#endif

Preserved versus scratch registers

The general RISC-V 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 t0 to t6, ft0ft11, a0a7, fa0fa7, and the return address registers, can be used as a scratch register by the function.

Preserved registers

Preserved registers are preserved across function calls (scratch registers are not). 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 s0s11 and fs0fs11, but not including the return address registers, are preserved registers.

Special registers

For some registers, you must consider certain prerequisites:

  • The stack pointer register (sp/x2) must at all times point to or below the last element on the stack, and be aligned to an even 16-byte boundary.

  • The global pointer register (gp/x3) and thread pointer register (tp/x4) must never be changed. They are set up by the runtime environment. In the eventuality of an interrupt, the register must have a specific value.

  • The return address register (ra/x1) holds the return address at the entrance of a function.

Function entrance

Parameters can be passed to a function using one of these basic methods:

  • In registers

  • On the stack

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:

  • Structure types—struct, union, and classes

  • Unnamed parameters to variable length (variadic) functions; in other words, functions declared as myFunc(param1, ...), for example printf.

Note

Interrupt functions cannot take any parameters.

Hidden parameters

In addition to the parameters visible in a function declaration and definition, there can be hidden parameters:

  • If the function returns an aggregate value, the memory location where the structure will be stored is passed in the register a0 (in effect as the first parameter).

Register parameters

The registers available for passing parameters are:

Parameters

Passed in registers

Integer and pointer values

a0a7

Floating-point values (if supported by an FPU extension, see below)

fa0fa7

Table 66. Registers used for passing parameters  


The assignment of registers to parameters is a straightforward process. Traversing the parameters from left to right, each is assigned to the available argument registers. Integer and pointer values are passed in registers a0a7. Floating-point values are passed in registers fa0fa7 if the corresponding floating-point type is supported by an FPU extension (soft-fpu values are treated as integer scalars of the same size).

Scalars smaller than 32 bits are zero- or sign-extended, depending on their type. On RV64, 32-bit scalars are sign-extended to 64 bits, regardless of type.

Scalars of 2*XLEN bits are passed in register pairs (ai, ai+1) where i is even. The least significant half is in ai. A register that is “skipped” by a register pair parameter is used for the first following parameter that fits in a register. (XLEN is the width of an x register in bits. For an RV32 core, this is 32, for an RV64 core, it is 64.)

If no more registers are available, remaining parameters are passed as stack parameters.

Stack parameters and layout

Stack parameters are stored in the main memory, starting at the location pointed to by the stack pointer register (sp). 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 location on the stack, aligned to the largest of alignment of the parameter type and 4.

The stack pointer itself is always aligned to 16 bytes.

This figure illustrates how parameters are stored on the stack:

StackImage_01.png

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 types

Returned in registers

8- to 32-bit scalar values

a0

64-bit scalar values (on RV32)

a0,a1

64-bit scalar values (on RV64)

a0

Floating-point values (if supported by an FPU extension)

fa0

Table 67. Registers used for returning values  


Aggregate values are returned by a hidden (first) pointer parameter to the function.

Scalars smaller than 32 bits are zero- or sign-extended, depending on their type. On RV64, 32-bit scalars are sign-extended to 64 bits, regardless of type.

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 in the return address register (ra).

Typically, a function returns by using the ret instruction.

If a function is to call another function, the original return address must be stored somewhere. This is normally done on the stack, for example:

            name    call
            section `.text`:CODE
            extern  func

            addi    sp, sp, -16
            sw      ra, 12(sp)

            ; Do something here.

            lw       ra, 12(sp)
            addi     sp, sp, 16

            ret

            end

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 a0, and the return value is passed back to its caller in the same register.

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(2)
            addi    a0, a0, 1
            ret
            end
Example 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 place the contents of the structure at the top of the stack. The y parameter is put in register a0. The return value is passed back to its caller in the a0 register.

Example 3

The function below will return a structure of type struct MyStruct.

struct MyStruct 
{ 
  int mA[20]; 
};

struct MyStruct MyFunction(int x);

It is the responsibility of the calling function to allocate a memory location for the return value and pass a pointer to it as a hidden first parameter. The pointer to the location where the return value should be stored is passed in a0. The caller is not required to preserve the value in a0. The parameter x is passed in a1.

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 a0, and the return value is returned in a0.