The compiler partitions the MAXQ20 general purpose registers into two sets.

The registers in the first set, A[4] through A[7], are used for parameter passing and returning function results and are not preserved across functions calls. In addition the registers DP[0], DP[1], LC[0], LC[1], BP, OFFS, AP, and GR are not preserved across a function call.

The registers in the second set, A[0]A[3] and A[9]A[15], are used for register variables, working storage, and temporary results and must be preserved across function calls.

Fixed registers

For correct execution of C code, the compiler makes a number of assumptions about the state of the MAXQ20 processor during program execution. It does this both to reduce code size and increase execution speed. Usually, you won't need to be aware of what assumptions the C compiler makes but if you are interfacing C code to assembly language subroutines, or calling C code from your own assembly language subroutines, you must be aware of the following points.

The control registers APC and DPC should always be set in the following modes for correct execution of C programs:

In particular, note that if you call a C subroutine from assembly code, you must ensure that both APC and DPC are set as described above before making the call to the function.

CrossWorks uses the accumulator A[8] as a soft stack pointer. A[8] is a word-based pointer into data memory for creating stack frames in memory. The soft stack descends from high to low memory. On function entry, A[8] is the word address of the lowest used word in the stack frame.

If you intend to call C code from a hand-written assembly code interrupt service routine, you must first ensure that you drop A[8] by at least 16 words. This is because there can be up to 16 words of valid data below A[8] as a consequence of normal code generation. Thus, your interrupt service routine must look something like this:

ISR:    push    PSF       ; save processor state
        push    APC
        push    DPC
        move    APC, #0   ; APC.0=APC.1=0
        move    DPC, #18H ; DPC.2=0; DPC.3=DPC.4=1
        move    AP, #8    ; point to A[8]
        sub     #16       ; A[8] = A[8]-16
        call    _c_func   ; call C code
        move    AP, #8    ; point to A[8]
        add     #16       ; restore A[8]
        pop     DPC       ; restore processor state
        pop     APC
        pop     PSF
        reti

Parameter passing

The compiler uses the scratch registers to pass values to the called routine for all parameters of simple data type. If there are not enough scratch registers to hold all parameter data to be passed to the called routine, the excess data are passed on the stack.

Simple data types which require more than a single word of storage are passed in register pairs or register quads. The register requirement for the basic data types are:

Allocation of the scratch registers for function calls proceeds in a left-to-right fashion, starting with register A[7] and progressing in reverse order to A[4]. The compiler tries to fit each parameter into the scratch registers and, if it can, allocates those registers to the incoming parameter. If the parameter requires more scratch registers than are free, it is noted and is passed on the stack. All parameters which are passed on the stack are pushed in reverse order.

Function return values

The compiler uses the scratch registers to return values to the caller.

Examples

This section contains some examples of the calling convention in use.

Example #1

void fun1(int u, int v);

Reading from left to right, the parameter u is passed in register A[7] and v is passed in A[6]. The scratch registers A[4] and A[5] are not used to pass parameters and can be used in fun1 without needing to be preserved.

Example #2

void fun1(int u, long v, int w);

The parameter u is passed in register A[7]. Because v requires two registers to hold its value it is passed in the register pair A[6]A[5] with A[6] holding the high part of v and A[5] the low part. The final parameter w is passed in A[4].

Example #3

void fun1(int u, long v, int w, int x);

The parameter u is passed in register A[7]. Because v requires two registers to hold its value, it is passed in the register pair A[6]A[5] with A[6] holding the high part of v and A[5] the low part. Parameter w is passed in A[4]. As all scratch registers are now used, x is placed onto the software stack.

Example #4

void fun1(int u, long v, long w);

The parameter u is passed in register A[7]. Because v requires two registers to hold its value it is passed in the register pair A[6]A[5] with A[6] holding the high part of v and A[5] the low part. When considering w, there is only one free scratch register left, which is A[4]. The compiler cannot fit w into a single register and therefore places the argument onto the software stack—the compiler does not split the value into two and pass half in a register and half on the software stack.

Example #5

void fun1(int u, long v, long w, int x, int y);

The parameter u is passed in register A[7]. Because v requires two registers to hold its value it is passed in the register pair A[6]A[5] with A[6] holding the high part of v and A[5] the low part. When considering w, there is only one free scratch register left, which is A[4]. The compiler cannot fit w into the single register A[4] and therefore places the argument onto the software stack. When considering x, the compiler sees that A[4] is unused and so passes x in A[4]. All scratch registers are used when considering y, so the argument is placed onto the software stack. The parameters w and x are pushed onto the stack before the call and are pushed in reverse order, with y pushed before w.

This example shows two parameters, w and y, that are passed to the called routine on the stack, but they are separated by a parameter x that is passed in a register.