3.3. Function Structure and Calling Conventions

The C/C++ compiler imposes a strict set of rules on function calls. Except for special run-time support functions, any function that calls or is called by a C/C++ function must follow these rules. Failure to adhere to these rules can disrupt the C/C++ environment and cause a program to fail.

There are a few things to be aware of when working with stack memory on TI C29x devices:

  • A15 is used as the stack pointer (SP). The SP points to the next empty location.

  • The stack grows “down” (from low to high addresses).

  • The stack is 64-bit aligned on function entry/exit.

This document uses the following terminology to describe the function-calling conventions of the C/C++ compiler:

  • Argument block: The part of the local frame used to pass arguments to other functions. This block is allocated to the largest size required of all calls in the function. It is allocated on function entry and deallocated on function exit. It is populated by moves or copies of values prior to the function call. (This is different from other targets, which push/pop onto the stack on-demand.)

  • Register save area: The part of the local frame into which caller-saved registers are copied when the program calls a function and from which values are restored to registers when the program returns control to the caller.

  • Caller-saved (alternately, save-on-call) registers: The callee function does not preserve values in these registers; therefore, the caller function must save them, potentially to the register save area if their values need to be preserved.

  • Callee-saved (alternately, save-on-entry) registers: The callee function must preserve the values in these registers. If the callee function modifies these registers, it saves the value to the stack and restores the value when it returns control to the caller.

  • Calling Convention: A calling convention is a description of how arguments are passed from caller to callee, and how values are returned from callee back to caller.

Due to the TI C29x security subsystem, there are multiple calling conventions:

  • Unprotected Calls

    • Caller-saved registers:

      • D0 - D9, XD0 - XD8

      • A0 - A9, XA0 - XA8

      • M0 - M25, XM0 - XM24

      • TA0-TA4, TDM0-TDM4

    • Callee-saved registers:

      • D10 - D15, XD10 - XD14

      • A10 - A14, XA10 - XA12

      • M26 - M31, XM26 - XM30

    • Argument registers:

      • A4-A9

      • D0-D7, XD0-XD4

      • M0-M7, XM0-XM6

      • The stack may be used to store arguments beyond the above available registers listed, see Arguments below.

    • Return registers:

      • A4

      • D0, XD0

      • M0, XM0

  • Protected Calls

    • Caller-saved registers:

      • All registers live across a protected call are caller-saved

    • Callee-saved registers:

      • No registers are saved by the callee

    • Argument registers:

      • A4-A9

      • D0-D7, XD0-XD4

      • M0-M7, XM0-XM6

      • The stack may not be used to store arguments beyond the above available registers, see Arguments below.

    • Return registers:

      • A4

      • D0, XD0

      • M0, XM0

The following figure shows stack usage before and after a typical function call.

../../_images/stack_func_c29.png

Figure 3.6 Use of the Stack During a Function Call

Note

  • The A15 register is used as the stack pointer (SP); SP points to the next empty location.

  • Stack grows from low to high addresses.

  • Stack is 64-bit aligned.

3.3.1. Arguments

Values passed from caller to callee follow a strict set of rules. A single unique location is assigned for each argument; this location does not vary program-to-program. See Examples 1 and 2.

  • Arguments are assigned from first to last, and are assigned to the first valid and available register.

  • Pointer arguments are assigned to A4-A9, in increasing order.

  • Integer arguments are assigned to D0-D7, in increasing order. A 64-bit integer is assigned to a pair of registers.

  • Floating-point arguments are assigned to M0-M7, similar to integer arguments.

Example 1:

void foo(int a, long long b, int c, int d, int e)
  • a, a 32-bit integer, is assigned to D0.

  • b, a 64-bit integer, must be assigned to a pair. The next available pair is XD2, so b is assigned to XD2 and D1 is left open.

  • c, a 32-bit integer, is assigned to the next free 32-bit register, D1.

  • d and e, 32-bit integers, are assigned to the next available registers. D2 and D3 are already assigned, so they are placed in D4 and D5, respectively.

Example 2:

void bar(int x, long long y, double z, char *h)
  • x, a 32-bit integer, is assigned to D0.

  • y, a 64-bit integer, is assigned to XD2, as above.

  • z, a 64-bit float, is assigned to the first available floating-point register pair, XM0.

  • h, a 32-bit pointer, is assigned to the first available pointer register, A4.

Variadic arguments (ellipsis “…”): Variadic arguments, such as those accepted by functions like printf(), are accessed via macros such as va_start, va_end, and va_arg. (See Variadic functions.) Every argument passed as part of a set of variadic arguments skips the register assignment phase. Instead it is assigned to the caller’s argument block as if there were no valid registers remaining.

Other types: Types that do not fit into the above classifications–such as classes, structures, or union types–are assigned locations in the caller’s argument block. Each is 8-byte-aligned, and values are copied into the block on call.

Running out of registers: Functions may have more arguments than there are available registers. In this case, such arguments are assign to locations in the caller’s argument block. See Examples 3 and 4.

Example 3:

void baz(int *a, int *b, int *c, int *d, int *e, int *f, int *g)
  • Arguments a through f are all 32-bit pointers and are assigned to A4-A9, respectively

  • Argument g does not have a valid register in A4-A9. It is instead treated like a 32-bit integer and assigned to D0.

Example 4:

void fizz(long long x, long long y, long long z, long long h)
  • Arguments x, y, and z, 64-bit integers, are assigned to XD0, XD2, and XD4, respectively

  • Argument h, a 32-bit integer, has no valid register remaining. It is passed as the first 4 bytes on the caller’s argument block.

3.3.2. Returned Values

Values returned from callee to caller follow a strict set of rules. A single unique location is assigned for the function’s returned value and does not vary program-to-program.

  • Pointer values are returned in A4.

  • 32-bit integer values are returned in D0.

  • 64-bit integer values are returned in XD0.

  • 32-bit floating point values are returned in M0.

  • 64-bit floating point values are returned in XM0.

Other types: Functions that return a type that does not fit into one of the above classifications–such as structures–allocate space on the caller’s argument block into which the callee copies the returned value. In the following example, the returned structure is treated as a leading pointer argument in the function’s argument list and is no longer treated as a returned value.

struct X foo(int a, char *b)

An equivalently handled call would be:

void foo(struct X *ptr, int a, char *b)
  • The ptr argument, a 32-bit pointer, is assigned to the first available pointer register, A4.

  • The a argument, a 32-bit integer, is assigned to D0.

  • The b argument, a 32-bit pointer, is assigned to A5, not A4 as it would be normally.

The callee writes to this pointer argument, which the caller can read from on return to extract the value.

3.3.3. How a Function Makes a Call

A parent function) performs the following tasks when it calls another function (child function).

  1. The call instruction pushes the 4-byte RPC onto the stack and increments the SP by 8 bytes, not by 4 bytes. This is so the stack remains 8-byte aligned. The RPC being saved is the return address of the caller.

  2. The call instruction then sets RPC to the new return address (the return address of the callee).

  3. The caller function is responsible for saving and restoring caller-saved registers whose values must be preserved across the call.

  4. The caller copies arguments to registers and stack locations, as detailed in Arguments.

  5. The caller issues a call instruction and transfers control to the callee.

3.3.4. How a Callee Function Responds

A function (callee function) must perform the following tasks in response to being called:

  1. The callee function allocates memory for the local variables and argument block by adding a constant to the SP. This constant is the sum of:

    • The size of the register save area for callee-saved registers.

    • The size of the local variables whose lifetimes have not been optimized to be entirely in-register.

    • The maximum size of all the argument blocks for each function called by the callee.

    • Any extra size required to align to an 8-byte boundary.

  2. The callee function saves each callee-saved register that it uses/modifies to the stack. This list can vary based on optimization.

  3. The callee function executes the code for the function.

  4. The callee function assigns the return value to a register or copies the value onto the stack, as detailed in Returned Values.

  5. The callee function restores all registers that were saved in step 2.

  6. The callee function deallocates the frame and argument block by subtracting the constant computed in step 1. Deallocation can occur either separate from the return instruction via subtraction of the constant from A15 or as part of the return instruction using the ADDR1 addressing mode. If the return instruction is the variant that does not adjust the SP after popping RPC, 8 is added to the constant computed in step 2.

  7. The return instruction loads the program counter (PC) with the return address in the RPC register before loading the previous return address (saved to the stack by the call instruction) from the stack into the RPC register.