Introduction to Computer Organization

ISA: Translation Software – Assembler, Linker, Loader, and More

Recap

ARM Linux Memory Map

  • The binary executable (text instructions) stores:
    • Global variables
    • Strings
    • Static local variables
  • Above that is the static data
    • Static variables
  • Then there is the heap
    • Dynamically allocated memory
  • Finally there is the stack
    • Has stack frames for function calls
      • Stores variables
      • Stores contents of registers

Memory map

Need to Save Registers

The need for storing contents of registers:

What happens to the values in registers when we make a function call? Assume x in foo() and y in bar() happen to be allocated to the same register r1.

void foo() {
    int x = 1;
    bar(x);
    x = x + 1; // the value of x after this is 2
}

void bar(int k) {
    int y = 2;
    y++;
}

See the problem? If not, look at this:

void foo() {
    r1 = 1;
    bar(r1);
    r1 = r1 + 1; // the value of r1 should be 2 again
}

void bar(int k) {
    r1 = 2;
    r1 = r1 + 1;
}

Each function call makes an independent decision for which register to allocate a variable. bar() then chooses to use the same register r1 to store a totally different variable, so then after the bar(r1) call, the value of r1 would be 3 at the end of foo()! Clearly not the right answer.

The problem here is that the functions are not preserving the values in the registers across the function call.

The question this lecture attempts to answer:

How can we reduce the number of function calls we need to save and restore register values?

There are static instructions and dynamic instructions. The dynamic instruction count is the actual number of instructions executed by the CPU for a specific program execution, whereas the static instruction count is the number of instruction the program has.

We usually use dynamic instruction count as if for example you have a loop in your program then some instructions get executed more than once. Also, in the presence of branches, some instructions may not be executed at all.

We want to use as little dynamic instructions as possible, even if this means more static instructions.

How to Save Registers During a Call

Callers and Callees

If the function foo() calls bar(), then foo() is a caller and bar() is a callee.

What if bar() calls zoo() after?

  • foo() is caller
  • bar() is caller and callee
  • zoo() is the callee

Storing Registers

Natural solution: store the variables before a function call, and restore them after a function call

Example

foo() {
    r0 = 5;
    r4 = -1;
    bar();
    r3 = r0 + r4;
}

bar() {
    r0 = 10;
    r4 = 5;
}

Here's some pseudocode for the assembly code:

foo() {
    r0 = 5;
    r4 = -1;
    save r0; i.e., str r0, [r13, #20]
    save r4; i.e., str r4, [r13, #24]
    bar();
    restore r0; i.e., ldr r0, [r13, #20]
    restore r4; i.e., ldr r4, [r13, #24]
    r3 = r0 + r4;
}

bar() {
    save r4; i.e., str r4, [r13, #8]
    r0 = 10;
    r4 = 5;
    restore r4; i.e., ldr r4, [r13, #8]
}

If a variable is dead, you want to use caller save. Would you want to use callee save? Let's say that you have the following code:

foo() {
    for(int i = 0; i < n; i++) {
        bar();
    }
}

If you performed callee save, then you would be using \(2n\) instructions rather than only \(2\) instructions.

Another Example

foo() {
    a = ...
    b = ...
    bar();
    ... = a;
    ... = b;
    for(1 to 15) {
        c = ...
        d = ...
        ... = c;
        printf();
        ... = d;
    }
}

Let's say you have 2 caller and 2 callee registers.

  • Is the value c live across the function call printf()? No! The value never got read after this. The value is dead. If c is dead, and you assign it to a caller save register, you don't have to save it.
  • If you had d in caller save, you would have to execute \(2*15\) instructions. Let's try callee save. You would have to just save d once and restore d once.