Skip to main content
\(\newcommand{\doubler}[1]{2#1} \newcommand{\binary}{\texttt} \newcommand{\hex}{\texttt} \newcommand{\octal}{\texttt} \newcommand{\prog}{\texttt} \newcommand{\lt}{<} \newcommand{\gt}{>} \newcommand{\amp}{&} \)

Section13.2Accessing Arguments in a Function

Registers r0r3 and a portion of the call stack are used for the activation record. The area of the stack used for the activation record is called a stack frame. A function sets up its own stack frame and usually stores the following information in it:

  1. The return address back to the calling function.

  2. The calling function's frame pointer.

  3. Register values that must be saved for the calling function.

  4. Local variables for the current function.

Arguments (or their addresses) beyond those that fit within registers r0r3 are placed on the stack by the calling function before branching to the called function. In these cases the calling function begins the creation of the stack frame. An argument that is less that 32 bits, for example a char, is passed in a full 32-bit word.

Listing 13.2.1 shows a program that passes nine arguments to a function, sumNine.

/* nineInts1.c
 * Declares and adds nine integers.
 * Bob Plantz - 28 July 2016 
 */
#include <stdio.h>
#include "sumNine1.h"

int main(void)
{
  int total;
  int a = 1;
  int b = 2;
  int c = 3;
  int d = 4;
  int e = 5;
  int f = 6;
  int g = 7;
  int h = 8;
  int i = 9;
   
  total = sumNine(a, b, c, d, e, f, g, h, i);
  printf("The sum is %i\n", total);

  return 0;
}
/* sumNine1.h
 * Computes sum of nine integers.
 * Bob Plantz - 4 June 2016
 */
#ifndef SUMNINE_H
#define SUMNINE_H
int sumNine(int one, int two, int three, int four, int five,
           int six, int seven, int eight, int nine);
#endif
/* sumNine1.c
 * Computes sum of nine integers.
 * Bob Plantz - 28 July 2016 
 */
#include <stdio.h>
#include "sumNine1.h"

int sumNine(int one, int two, int three, int four, int five,
           int six, int seven, int eight, int nine)
{
  int x;

  x = one + two + three + four + five + six
            + seven + eight + nine;
  return x;
}
Listing13.2.1Sum the digits \(0\)–\(9\text{.}\) There are three files here, nineInts1.c, sumNine1.h, and sumNine1.c. (C)

The subfunction here, sumNine1.c, is accompanied by a header file. The header file contains the function declaration without the function definition. Including the header file in the calling function's file provides the compiler with a prototype of how the subfunction is called. Thus the compiler knows how to generate the assembly language code to call the subfunction.

Note that we have also included the header file in the file that defines the subfunction. This provides a double check that the function declaration in the header file matches the function definition header in its definition.

When writing in assembly language, you do not use header files to provide declarations for the functions you are calling. Since there is no compilation phase, you need to understand the calling sequence and write the correct code yourself. On the other hand, if you write a function in assembly language that you wish to make callable from C code, you need to supply a header file with the function declaration so the compiler knows how to call your assembly language function.

Listing 13.2.2 shows the assembly language generated by the compiler for the program in Listing 13.2.1.

        .arch armv6
        .fpu vfp
        .file   "nineInts1.c"
        .section  .rodata
        .align  2
.LC0:
        .ascii  "The sum is %i\012\000"
        .text
        .align  2
        .global main
        .type   main, %function
main:
        @ args = 0, pretend = 0, frame = 40
        @ frame_needed = 1, uses_anonymous_args = 0
        stmfd   sp!, {fp, lr}
        add     fp, sp, #4
        sub     sp, sp, #64     @@ space for locals and args
        mov     r3, #1
        str     r3, [fp, #-8]
        mov     r3, #2
        str     r3, [fp, #-12]
        mov     r3, #3
        str     r3, [fp, #-16]
        mov     r3, #4
        str     r3, [fp, #-20]
        mov     r3, #5
        str     r3, [fp, #-24]
        mov     r3, #6
        str     r3, [fp, #-28]
        mov     r3, #7
        str     r3, [fp, #-32]
        mov     r3, #8
        str     r3, [fp, #-36]
        mov     r3, #9
        str     r3, [fp, #-40]
        ldr     r3, [fp, #-24]
        str     r3, [sp]         @@ arg e
        ldr     r3, [fp, #-28]
        str     r3, [sp, #4]     @@ arg f
        ldr     r3, [fp, #-32]
        str     r3, [sp, #8]     @@ arg g
        ldr     r3, [fp, #-36]
        str     r3, [sp, #12]    @@ arg h
        ldr     r3, [fp, #-40]
        str     r3, [sp, #16]    @@ arg i
        ldr     r0, [fp, #-8]    @@ arg a
        ldr     r1, [fp, #-12]   @@ arg b
        ldr     r2, [fp, #-16]   @@ arg c
        ldr     r3, [fp, #-20]   @@ arg d
        bl      sumNine
        str     r0, [fp, #-44]
        ldr     r0, .L3
        ldr     r1, [fp, #-44]
        bl      printf
        mov     r3, #0
        mov     r0, r3
        sub     sp, fp, #4
        @ sp needed
        ldmfd   sp!, {fp, pc}
.L4:
        .align  2
.L3:
        .word   .LC0
        .ident  "GCC: (Raspbian 4.9.2-10) 4.9.2"
        .arch armv6
        .fpu vfp
        .file   "sumNine1.c"
        .text
        .align  2
        .global sumNine
        .type   sumNine, %function
sumNine:
        @ args = 20, pretend = 0, frame = 24
        @ frame_needed = 1, uses_anonymous_args = 0
        @ link register save eliminated.
        str     fp, [sp, #-4]!
        add     fp, sp, #0
        sub     sp, sp, #28
        str     r0, [fp, #-16]   @@ save the arguments
        str     r1, [fp, #-20]   @@     passed in
        str     r2, [fp, #-24]   @@     registers
        str     r3, [fp, #-28]   @@     in local area
        ldr     r2, [fp, #-16]   @@ load arg one
        ldr     r3, [fp, #-20]   @@ and arg two
        add     r2, r2, r3       @@ r2 is subtotal
        ldr     r3, [fp, #-24]   @@ load arg three
        add     r2, r2, r3       @@ add to subtotal
        ldr     r3, [fp, #-28]   @@    etc...
        add     r2, r2, r3
        ldr     r3, [fp, #4]
        add     r2, r2, r3
        ldr     r3, [fp, #8]
        add     r2, r2, r3
        ldr     r3, [fp, #12]
        add     r2, r2, r3
        ldr     r3, [fp, #16]
        add     r2, r2, r3
        ldr     r3, [fp, #20]    @@ load arg nine
        add     r3, r2, r3       @@ add to subtotal
        str     r3, [fp, #-8]    @@ store in x
        ldr     r3, [fp, #-8]
        mov     r0, r3           @@ return x;
        sub     sp, fp, #0
        @ sp needed
        ldr     fp, [sp], #4
        bx      lr
        .ident  "GCC: (Raspbian 4.9.2-10) 4.9.2"
Listing13.2.2Sum the digits \(0\)–\(9\text{.}\) There are two files here, nineInts1.s and sumNine1.s. (gcc asm)

Listing 13.2.3 shows my assembly language version of the program in Listing 13.2.1. I have simplified the sumNine function by directly using the arguments passed in registers. The compiler version (Listing 13.2.2 first saved these arguments in the local stack frame, thus freeing up the registers for local use.

@ nineInts2.s
@ Sums 1 through 9
@ Bob Plantz - 5 August 2016

@ Define my Raspberry Pi
        .cpu    cortex-a53
        .fpu    neon-fp-armv8
        .syntax unified         @ modern syntax

@ Useful source code constants
        .equ    a,-8
        .equ    b,-12
        .equ    c,-16
        .equ    d,-20
        .equ    e,-24
        .equ    f,-28
        .equ    g,-32
        .equ    h,-36
        .equ    i,-40
        .equ    total,-44
        .equ    locals,40
@ Need 5 args on stack for sumNine function
        .equ    arg5,0
        .equ    arg6,4
        .equ    arg7,8
        .equ    arg8,12
        .equ    arg9,16
        .equ    argSz,24  @ 5x4, 8-byte aligned

@ Program constant data
        .section  .rodata
        .align  2
resultMsg:
        .asciz  "The sum is %i\n"

@ The code
        .text
        .align  2
        .global main
        .type   main, %function
main:
        stmfd   sp!, {fp, lr}   @ save regs
        add     fp, sp, 4       @ our frame pointer
        sub     sp, sp, locals  @ space for locals

        mov     r3, 1           @ initialize local vars
        str     r3, [fp, a]     @ a = 1;
        mov     r3, 2
        str     r3, [fp, b]     @ b = 2;
        mov     r3, 3
        str     r3, [fp, c]     @ etc...
        mov     r3, 4
        str     r3, [fp, d]
        mov     r3, 5
        str     r3, [fp, e]
        mov     r3, 6
        str     r3, [fp, f]
        mov     r3, 7
        str     r3, [fp, g]
        mov     r3, 8
        str     r3, [fp, h]
        mov     r3, 9
        str     r3, [fp, i]     @ i = 9;

@ Function call:  sumNine(a, b, c, d, e, f, g, h, i);
        sub     sp, sp, argSz   @ space for args
        ldr     r3, [fp, e]     @ set up args for call
        str     r3, [sp, arg5]  @ e is 5th arg
        ldr     r3, [fp, f]    
        str     r3, [sp, arg6]  @ f is 6th arg
        ldr     r3, [fp, g]
        str     r3, [sp, arg7]  @ etc...
        ldr     r3, [fp, h]
        str     r3, [sp, arg8]
        ldr     r3, [fp, i]
        str     r3, [sp, arg9]
        ldr     r0, [fp, a]     @ args 1 - 4
        ldr     r1, [fp, b]     @    go in
        ldr     r2, [fp, c]     @    regs
        ldr     r3, [fp, d]     @    0 - 3
        bl      sumNine
        add     sp, sp, argSz   @ restore sp
        str     r0, [fp, total] @ total returned in r0

        ldr     r0, resultMsgAddr @ print result
        ldr     r1, [fp, total]
        bl      printf

        mov     r0, 0           @ return 0;
        add     sp, sp, locals  @ deallocate local var
        ldmfd   sp!, {fp, lr}   @ restore caller's info
        bx      lr              @ return

        .align  2
resultMsgAddr:
        .word   resultMsg
@ sumNine2.s
@ Computes sum of nine integers.
@ Calling sequence:
@        Four ints in r0 - r3
@        Five ints pushed onto stack
@        bl      sumNine
@        Sum returned in r0
@ Bob Plantz - 5 August 2016

@ Define my Raspberry Pi
        .cpu    cortex-a53
        .fpu    neon-fp-armv8
        .syntax unified         @ modern syntax

@ Useful source code constants
        .equ    five,4          @ args 5 - 9
        .equ    six,8
        .equ    seven,12
        .equ    eight,16
        .equ    nine,20

        .text
        .align  2
        .global sumNine
        .type   sumNine, %function
sumNine:
        stmfd   sp!, {fp, lr}   @ save caller's info
        add     fp, sp, 4       @ set our frame pointer
@ Sum four register arguments      
        add     r0, r0, r1      @ subtotal = one + two
        add     r0, r0, r2      @ subtotal += three
        add     r0, r0, r3      @ subtotal += four
@ Add in five arguments from stack
        ldr     r3, [fp, five]  @ load five
        add     r0, r0, r3      @ subtotal += five
        ldr     r3, [fp, six]   @ load six
        add     r0, r0, r3      @ subtotal += six
        ldr     r3, [fp, seven] @ load seven
        add     r0, r0, r3      @ subtotal += seven
        ldr     r3, [fp, eight] @ load eight
        add     r0, r0, r3      @ subtotal += eight
        ldr     r3, [fp, nine]  @ load nine
        add     r0, r0, r3      @ total += nine
                                @    ready for return
        ldmfd   sp!, {fp, lr}   @ restore caller's info
        bx      lr              @ return
Listing13.2.3Sum the digits \(0\)–\(9\text{.}\) There are two files here, nineInts2.s and sumNine2.s. (prog asm)

Because only four registers are available for passing arguments, the main function places the remaining five arguments on the stack, as shown in Figure 13.2.4. The stack pointer is pointing to the top of the stack of these arguments when the subfunction is called.

<<SVG image is unavailable, or your browser cannot render it>>

Figure13.2.4The state of the stack in the program from Listing 13.2.3 immediately before calling the subfunction. The first four arguments are in registers, the remaining five on the stack.

The gcc compiler chose to allocate space on the stack for both the local variables and the stack arguments at the same time, but I chose to separate the two operations. Placing the stack argument allocation near the call to the function:

@ Function call:  sumNine(a, b, c, d, e, f, g, h, i);
        sub     sp, sp, #argSz  @ space for args

helps to prevent programming errors if the call to sumNine is changed. This technique also eliminates the need to read through the entire function before computing the overall amount of memory required on the stack. Also notice that I deallocate the space on the stack used for passing arguments immediately upon return from the function:

bl      sumNine
add     sp, sp, argSz   @ restore sp

This technique treats each function call more like a single function call statement in a high-level language. It would be highly unusual that the additional stack pointer operations would have a measurable effect on program performance.

The prologue in the subfunction:

stmfd   sp!, {fp, lr}   @ save caller's info
add     fp, sp, 4       @ set our frame pointer

saves the caller's frame pointer and the address in the link register (the return address) on the stack, and then sets the frame pointer for the subfunction. This places the stack in the state shown in Figure 13.2.5. Within the called function, we access the arguments relative to the frame pointer.

<<SVG image is unavailable, or your browser cannot render it>>

Figure13.2.5The state of the stack in the program from Listing 13.2.3 after executing the prologue in the subfunction. The arguments on the stack are accessed relative to the frame pointer.

The overall pattern of a stack frame is shown in Figure 13.2.6. The r11 register serves as the frame pointer to the stack frame. Once the frame pointer address has been established in a function, its value must never be changed. The frame pointer points to the calling function's frame pointer, and the return address is located \(-4\) bytes offset from the frame pointer. Arguments to the function are positive offsets from the frame pointer, and local variables are negative offsets from the frame pointer.

<<SVG image is unavailable, or your browser cannot render it>>

Figure13.2.6Overall layout of the stack frame.

It is essential that you follow the register usage and argument passing disciplines precisely. Any deviation can cause errors that are very difficult to debug.

  1. In the calling function:

    1. Assume that the values in the r0, r1, r2, and r3 registers will be changed by the called function.

    2. The first four arguments are passed in the r0, r1, r2, and r3 registers in left-to-right order.

    3. Arguments beyond four are stored on the stack as though they had been pushed onto the stack in right-to-left order. Yes, the order is opposite.

    4. Use the bl instruction to invoke the function you wish to call.

  2. Upon entering the called function:

    1. Save the value in the link register (r14), the caller's frame pointer (in register r11), and contents of any registers that must be restored by pushing them onto the stack using the stmfd instruction.

    2. Establish a new frame pointer address by adding \(4 \times (n - 1)\text{,}\) where \(n =\) number of registers pushed, to the address in the stack pointer (r13).

    3. Allocate space on the stack for all the local variables, plus any required register save space, by subtracting the number of bytes required from sp, observing stack alignment restrictions.

  3. Within the called function:

    1. Do not use the stack pointer to access arguments or local variables. sp is pointing to the current bottom of the portion of the stack that is accessible to this function, observing the usual stack discipline.

    2. Never change the value in the frame pointer, r11.

    3. Local variables are on the stack and are accessed through negative offsets from the frame pointer.

    4. Arguments passed on the stack to the function are accessed through positive offsets from the frame pointer.

  4. When leaving the called function:

    1. Place the return value, if any, in r0.

    2. Deallocate the local variables by adding the same amount to sp that was subtracted at the beginning of the function.

    3. Pop the saved register values off the stack into the proper registers using the ldmfd instruction.

    4. The last value popped off the stack is the return address, which is popped into the lr, ready for the bx lr instruction.

Subsection13.2.1Exercises

The functions from Exercise 13.2.1.2 and Exercise 13.2.1.4 will be used for other exercises in subsequent chapters.

1

Enter the C program in Listing 13.2.1. Using the “-S” compiler option, compile it with differing levels of optimization, i.e., “-O1,” “-O2,” “-O3,” and discuss the assembly language that is generated. Compare the results with my solution in Listing 13.2.3. Is the optimized code easier or more difficult to read?

Solution
2

Write the function, writeStr, in assembly language. The function takes one argument, a char *, which is a pointer to a C-style text string. It displays the text string on the screen. It returns the number of characters displayed.

Demonstrate that your function works correctly by writing a main function that calls writeStr to display “Hello world” on the screen. Your main function can ignore the character count that is returned by writeStr.

Solution
3

Write the function, readLn, in assembly language. The function takes one argument, a char *, which is a pointer to a char array, for storing a text string. It reads characters from the keyboard and stores them in the array as a C-style text string. It does not store the ‘\n’ character. It returns the number of characters, excluding the NUL character, that were stored in the array.

Demonstrate that your function works correctly by writing a main function that prompts the user to enter a text string and then echoes the user's input, using the writeStr function from Exercise 13.2.1.2. When testing your program, be careful not to enter more characters than the allocated space. Explain what would occur if you did enter too many characters. Your main function can ignore the character count that is returned by writeStr and readLn.

Hint Solution
4

Modify the readLn function in Exercise 13.2.1.3 so that it takes a second argument, the maximum length of the text string, including the NUL character. Excess characters entered by the user are discarded.

Demonstrate that your function works correctly by writing a main function that prompts the user to enter a text string and then echoes the user's input, using the writeStr function from Exercise 13.2.1.2. When testing your program, be careful not to enter more characters than the allocated space. Your main function can ignore the character count that is returned by writeStr and readLn.

Hint 1 Hint 2 Solution
5

One problem with using writeLn in the solution to Exercise 13.2.1.4 is that it needs a newline after echoing the user's input string. Correct this problem. I do not recommend adding the newline character to the string before echoing it. See if you can solve the problem some other way.

Solution