CS 208 s20 — Implementing Procedure Calls
Table of Contents
1 Video
Here is a video lecture for the material outlined below. It covers CSPP sections 3.7.3 through 3.7.6 (p. 245–254). It contains sections on
- introduction & announcements (0:00)
- warmup (1:46)
- local variables on the stack (6:15)
- example of a pointer to a local variable (11:48)
- example of passing arguments on the stack (20:43)
- stack frames exercise (29:22)
- local variables in registers (31:31)
- example of callee-saved registers (33:46)
- recursive procedures (37:59)
- end notes (39:58)
The Panopto viewer has table of contents entries for these sections. Link to the Panopto viewer: https://carleton.hosted.panopto.com/Panopto/Pages/Viewer.aspx?id=e4368c56-cccd-4468-ae6a-abb501137a43
2 Annoucements
- Thank you for submitting midterm evaluations!
- Clarification: there won’t be any exams, just continued weekly quizzes
- This week's will be posted later today
- I have video office hours appointments!
- Link here or on the course web page
- Send me an email or Slack message to set up another time
- VM window super small?
- Likeliest solution is to install VirtualBox "Guest Additions" in your VM
- Other possibilities:
- Go to the VirtualBox Preferences » Display » Scale Factor = 200%.
- Go to the VM Settings » Display » Screen » Graphics Controller = VBoxVGA
- Clarification: there won’t be any exams, just continued weekly quizzes
3 Warmup
4 Procedures Continued
4.1 Local Storage on the Stack
- not all local variables are necessarily stored on the stack
- accessing registers is much faster than accessing memory, so the compiler will use a register for a local variable whenever possible
- sometimes we have to store things on the stack:
- when we run out of registers (we only have a fixed number of them, after all), we'll have to start using the stack
- if code generates a pointer to a local variable (via
&
operator), we have to store that variable on the stack- why? Because a register doesn't have an address, only a name.
- In order to have a pointer to something (i.e., a memory address for it), the value has to be in memory somewhere
- if our local variables are data structures like arrays or structs that don't make sense to store in a register
4.2 Examples
void swap(long *xp, long *yp) { long t = *xp; *xp = *yp; *yp = t; } long f(long x, long y) { swap(&x, &y); return x - y; }
swap(long*, long*): movq (%rdi), %rax movq (%rsi), %rdx movq %rdx, (%rdi) movq %rax, (%rsi) ret f(long, long): subq $16, %rsp // allocate space on the stack by moving the stack pointer (grows DOWN) movq %rdi, 8(%rsp) // move x onto the stack movq %rsi, (%rsp) // move y onto the stack movq %rsp, %rsi // copy the stack address for y to rsi leaq 8(%rsp), %rdi // copy the stack address for x in rdi call swap(long*, long*) movq 8(%rsp), %rax subq (%rsp), %rax addq $16, %rsp // free stack space by moving the stack pointer back up ret
void parse_five_numbers(char *input, int numbers[]) { int numScanned = sscanf(input, "%d %d %d %d %d", &(numbers[0]), &(numbers[1]), &(numbers[2]), &(numbers[3]), &(numbers[4])); if (numScanned < 5) printf("KABLOOEY!!!"); }
.LC0: .string "%d %d %d %d %d" .LC1: .string "KABLOOEY!!!" parse_five_numbers: subq $16, %rsp // allocate stack frame movq %rsi, %rdx // address of numbers[0] as third arg leaq 16(%rsi), %rax // load address of numbers[4] into rax leaq 4(%rsi), %rcx // address of numbers[1] as fourth arg pushq %rax // push address of numbers[4] onto the stack as seventh arg leaq 12(%rsi), %r9 // address of numbers[3] as sixth arg leaq 8(%rsi), %r8 // address of numbers[2] as fifth arg movl $.LC0, %esi // address of format string as second arg movl $0, %eax call __isoc99_sscanf // never loaded something into %rdi (first arg), why? // it's the same as the first arg to parse_five_numbers, so we just keep it addq $16, %rsp // deallocate stack frame cmpl $4, %eax jg .L1 movl $.LC1, %edi movl $0, %eax call printf .L1: addq $8, %rsp // cleaning up the pushq %rax from before ret
4.3 Stack Frames
Each node in the above tree represents a function call. How many stack frames will be created? How many will be present on the stack at one time?3
4.4 Local Storage in Registers
- The compiler has to make sure callee does not overwrite register values the caller plans to use later
- x86-64 has uniform conventions that all procedures must adhere to
%rbx
,%rbp
, and%r12
–%r15
are callee-saved registers (whenP
callsQ
,Q
must preserve these values)- preserving means not writing to that register or pushing its value onto the stack and popping it off to restore before returning (saved registers portion of the stack frame)
- all other registers besides the stack pointer
%rsp
are caller-saved (whenP
callsQ
,Q
is free to modify these registers, soP
must save any that it needs)
long P(long x, long y) { long u = Q(y); long v = Q(x); return u + v; }
P: pushq %rbp // save value of callee-saved register this procedure will use pushq %rbx // save value of callee-saved register this procedure will use movq %rdi, %rbp // %rdi is a caller-saved register, we'll need its value later so we // save it in a callee-saved register that Q will have to preserve movq %rsi, %rdi call Q movq %rax, %rbx // %rax is also caller-saved, so put in it callee-saved %rbx for // later use movq %rbp, %rdi call Q addq %rbx, %rax popq %rbx // restore value of callee-saved register this procedure used popq %rbp // restore value of callee-saved register this procedure used ret
4.5 Recursive Procedures
- the stack and register conventions facilitate recursion without needing any extra apparatus
long fact_rec(long n) { long result; if (n <= 1) result = 1; else result = n * fact_rec(n - 1); return result; }
fact_rec: cmpq $1, %rdi jg .L8 movl $1, %eax ret .L8: pushq %rbx movq %rdi, %rbx leaq -1(%rdi), %rdi call fact_rec imulq %rbx, %rax popq %rbx ret
4.6 Example in gdb
- Not in today's video, included here for completeness
- using
-tui
layout asm
thenlayout regs
to get assembly and register viewsfocus cmd
to have arrows go through command historywinheight regs -LINES
to have registers take up less of the screen- note
%rip
changes each step %rsp
changes when we callcall_inrc
even beforesub $0x10,%rsp
—why?Print
vseXamine
- display the value of an expression vs use the value of an expression as a memory address and display the value stored in memory there
p $rsp
displays the address of the top of the stackx $rsp
displays the value stored at the top of the stack
long increment(long* p, long val) { long x = *p; long y = x + val; *p = y; return x; } long call_incr() { long v1 = 208; long v2 = increment(&v1, 124); return v1+v2; } int main() { call_incr(); }
increment: movq (%rdi), %rax addq %rax, %rsi movq %rsi, (%rdi) ret call_incr: subq $16, %rsp movq $208, 8(%rsp) movl $124, %esi leaq 8(%rsp), %rdi call increment addq 8(%rsp), %rax addq $16, %rsp ret main: movl $0, %eax call call_incr movl $0, %eax ret
5 Homework
- CSPP practice problems 3.34 (p. 252) and 3.35 (p. 255)
- The week 5 quiz has been posted. It is due 9pm Wednesday, May 13.
- If you have't posted in the lab 2 check-in forum, do so today!
- DO NOT WAIT ANY LONGER TO START LAB 2 (if you haven't already started)
Footnotes:
Those instructions store the address of the popq %rax
instruction in %rax
.
call
pushes the address of the next instruction (the return address) onto the stack and then jumps to the specified target.
popq %rax
is the next instruction, so call next
pushes its address onto the stack, and then jumps to the next
label, which happens to be at popq %rax
.
popq %rax
then pops the value from the top of the stack into %rax
.
- Arguments for a function call (beyond the first six)
- Saved registers (callee-saved)
- Local variables
- Return address
Six stack frames will be created, one for each call. Four will be on the stack at one time, (e.g., slorp, squish, splat, printf), since once squish calls slop, splat and the first printf will have returned and been popped off the stack.