Skip to content

Procedure

  • Suppose procedure P calls procedure Q, and Q then executes and returns back to P.

Run-time Stack

  • C programs use the (run-time) stack for local variables, function parameters, and return addresses.

Jump Instructions

  • When procedure P calls procedure Q, it’ll push the return address onto the stack, indicating where within P the program should resume execution once Q returns

Control transfer

InstructionDescription
call LabelProcedure call
call *OperandProcedure call
retReturn from call
  • The instruction call Q pushes an address A onto the stack, sets the PC to the beginning of Q.

  • The pushed address A is referred as the return address and is computed as the address of the instruction immediately following the call instruction.

  • The counterpart instruction ret pops an address A off the stack and sets the PC to A.

  • Example: consider the following C code

long mult2(long, long);
void multstore(long x, long y, long *dest) {
long t = mult2(x, y);
*dest = t;
}

Jump Instructions

Jump Instructions

  • In this code, we can see that:

    • The callq instruction with address 0x400563 in main calls function multstore.
    • The effect of call is to pushes the next instruction address (0x400568) to top of the stack
    • Then jumps to the instruction in function multstore at address 0x400540 (by set this value to %rip register).
    • The execution of multstore continues until it hits the retq instruction at 0x40054d.
    • The instruction retq pops 0x400568 from the stack and jumps to this address (by set this value to %rip register), resuming the execution of main
  • Example: Jump Instructions

Data transfer

  • In addition to passing control to a procedure when called, and then back again when the procedure returns, procedure calls may involve passing data as arguments, and returning from a procedure may also involve returning a value.

  • With x86_64, up to 6 integral (integer and pointer) arguments can be passed via registers. Jump Instructions

  • When a function has more than 6 integral arguments, the other ones are passed on the stack.

Local Storage on the Stack (stores local variable)

  • Not enough registers to hold all of the local data.

  • The address operator & is applied to local variable, and hence we must be able to generate an address for it.

  • Some of the local variables are arrays or structures and hence must be accessed by array or structure references.

  • Portion of the stack frame labeled “Local variables”

  • Consider this example: Jump Instructions

  • Example 2:

Jump Instructions

Local storage in registers (stores local variable)

  • Callee-saved registers: %rbx, %rbp, %r12-%r15

  • When procedure P calls procedure Q, Q must preserve the values of these registers, ensuring that they have the same values when Q returns to P as they did when Q was called. Procedure Q can preserve a register value by either not changing it at all or by pushing the original value on the stack, altering it, and then popping the old value from the stack before returning. The pushing of register values has the effect of creating the portion of the stack frame labeled “Saved registers” in Figure 3.25. With this convention, the code for P can safely store a value in a callee-saved register (after saving the previous value on the stack, of course), call Q, and then use the value in the register without risk of it having been corrupted.

  • Consider the following example: Code demonstrating use of callee-saved registers

    • Initially, x is in %rdi, y is in %rsi, if we want to call Q(y), somehow we must pass the value of y into the %rdi register (1st arg), but before that we need to save the value x somewhere (%rbp in this case). Same goes for x.
    • Function P will use %rbp and %rbx registers to save x, y. Since they’re callee-saved, function P will have to pushing them to the stack (Saved registers portion).
    • At the end of the function, it restores the values from the stack into these 2 registers.

Recursive procedures

Variable-size stack frames

  • Consider an example of a function containing a variable-size array. Code example

  • The function declares local array p of n pointers, where n is given by the first argument. This requires allocation 8n bytes on the stack, where the value of n may vary from one call of the function to another.

  • The compiler therefore can’t determine how much space it must allocate for the function’s stack frame.

  • In addition, the program generates a reference to the address of the local variable i (so this variable must be stored on the stack).

  • During execution, the program must be able to access both local variable i and the elements of array p. On returning, the function must deallocate the stack frame and set the stack pointer to the position of the stored return address.

  • To manage a variable-size frame, x86_64 code usees register %rbp to serve as a frame pointer (or base pointer). When using a frame pointer, the stack frame is organized as shown below: Code example

    • The code starts by pushing the current value of %rbp onto the stack and setting %rbp to point to this stack position (lines 2–3).
    • Next, it allocates 16 bytes on the stack, the first 8 of which are used to store local variable i, and the second 8 of which are unused.
    • Then it allocates space for array p (lines 5–11).