Functioning Stacks

Assembler code is complex, and, therefore, adhering to conventions greatly aids reducing the time and effort needed for code-construction and debugging.

The most important convention is the function-calling convention, which details how a calling function, the caller, and a called function, the callee, should pass parameters and return values between them.

The key concept underlying the function-calling convention is the frame, a piece of the stack, allocated from the stack at run-time by the called function — each time the function is called — and used to store its variables.

This per-call frame allocation ensures that nested or recursive functional calls results in each instance of the called function, the callee, getting its own parameters and variables.

In a caller-callee scenario, the stack is organized into frames, with one frame existing for each active (not yet returned) function call.
In Y86, we use the ebp register to point to the first element in the current frame, and the addresses between the esp register and the ebp register represents a stack frame. Physically, a function’s frame is the area between the addresses contained in the stack pointer, controlled by the esp register and the frame pointer, controlled by the ebp register.

Unlike the esp register, which (normally) is manipulated implicitly through the pushl and popl instructions, the ebp register is manipulated only explicitly.

A key part of the function-calling convention is that the caller must clean up the stack after the function call, reflecting the fact that the called function has no way to know what to clean up.

Function-calling convention in Y86

So here, in all its beauty, is the function-calling convention in Y86. A sample Y86 program that ties it all together can be found below.

Caller’s preparation

  • Push parameters onto the stack, from right to left — Parameters are pushed onto the stack, one at a time, from right to left. The calling code must keep track of how many bytes of parameters have been pushed onto the stack so it can clean it up later.
  • Call the function using the call instruction — This causes the the program counter, pc, for the instruction after the call instruction to be pushed onto the stack, after which the stack pointer register is updated. After push of the program counter, pc, is complete, pc is set to the address of the called function, causing the caller to lose control and the callee to take charge.

Callee’s work

  • Save and update the frame pointer — We need a new local stack frame pointed to by the ebp register, so the first thing to do is to save the current ebp register, which points to the callers’s frame. After this, we need to make a new ebp register, by pointing it to the stack (through the esp register). Once the ebp register has been changed, the callee can now refer directly to the function’s arguments through offset, such as as 8(%ebp) and 12(%ebp). Note that 0(%ebp) points contains the old base pointer and 4(%ebp) is the stored pc, which will be used to return control to the caller once the callee issues the return command through the ret instruction.
  • Save registers used by callee — If callee uses any of the working registers, it has to save the old values first by storing them on the stack using the pushl instruction. However, there is no point in storing any register that is going to be used as a return value (normally the return value from the call is conveyed in the aix register
  • Allocate space for local variables — The callee may choose to use local variables. Space for such variables are allocated by simply decrementing the stack pointer by the amount of space required (note: this is an explicit manipulation of the esp register.) In Y86 such decrementing should always be done in four-byte chunks, and, so, the same result can be achieved by issuing an appropriate number of pushl instructions. The local variables are located on the stack between the ebp register and the esp register, and though it would be possible to refer to them as offsets from either one, by convention the ebp register is used. For instance, by convention -4(%ebp) refers to the first local variable.
  • Do whatever work is needed — The callee is free to use any of the registers that had been saved onto the stack upon entry, but it must not change the stack pointer.
  • Release local storage — The process of allocating space for local variables must be reversed. This is done by adding to the esp register the same amount which was subtracted previously, or, alternatively, issuing an appropriate number of popl instructions.
  • Set return code if applicable — The callee communicates the return code — if any — in a register (by convention the eax register.)
  • Restore saved registers — Each register saved onto the stack upon entry must be restored from the stack.
  • Restore the old base pointer — The ebp register must be restored, so as to actualize the caller’s frame. This is done by simply popping the ebp register of the stack.
  • Return from the function — As the last thing, the callee must issue the ret instruction, which pops the stored pc from the stack and jumps to the instruction pointed to by the pc.

Caller’s cleaning job

  • Clean up pushed parameters — Using the popl instruction the caller must remove the parameters placed onto the stack.
  • Examine return code if applicable — The caller examines the relevant return code register (by convention, the eax register) and acts upon the result code stored therein.

Putting it all together

The program below illustrates how to use the stack for recursive function calls in Y86.

The program includes 5 calls of a recursive function, with each call resulting in a decrementing counter being stored in memory. The 5 stored counters are visible in memory, at location 0x01f8 through 0x0208 after the completion of the program run.

To illustrate how to handle local variables, the program’s recursive function allocates 2 words worth of space for local variables and store values in these.