The normal strategy:
Another way to write functions is the way C pretends to work: all local variables live on the stack. In that plan:
We also should decide which temporary values we produce and where those will be stored. These can be allocated to temporary registers: %r11, %r10, %r9, %r8.
Strategies:
label:
# Prologue:
# Set up stack frame.
# Body:
# Just say "TODO"
# Epilogue:
# Clean up stack frame.
Example:
Pseudocode:
int a = 5;
int b = 3 * a + 1;
Mapping variables:
Assembly:
# int a = 5;
mov $5, -8(%rbp)
# int b = 3 * a + 1;
mov -8(%rbp), %r11
imulq $3, %r11
inc %r11
mov %r11, -16(%rbp)
// Case 1
if (x < y) {
y = 7;
}
// Case 2
if (x < y) {
y = 7;
}
else {
y = 9;
}
Variable Mapping:
Case 1:
# if (x < y)
mov -16(%rbp), %r10 # cmp can only take one indirect arg
cmp %r10, -8(%rbp) # cmp order backwards from C
jge else1: # condition reversed, skip block unless cond
# y = 7
movq $7, -16(%rbp) # need suffix to set size of "7"
else1:
...
Case 2:
# if (x < y)
mov -16(%rbp), %r10 # cmp can only take one indirect arg
cmp %r10, -8(%rbp) # cmp order backwards
jge else1: # condition reversed, skip block unless cond
# then {
# y = 7
movq $7, -16(%rbp) # need suffix to set size of "7"
j done1 # skip else
# } else {
else1:
# y = 9
movq $9, -16(%rbp)
# }
done1:
...
do {
x = x + 1;
} while (x < 10);
Variable Mapping:
loop:
add $1, -8(%rbp)
cmp $10, -8(%rbp) # reversed for cmp arg order
jge loop # sense reversed
# ...
while (x < 10) {
x = x + 1;
}
Variable mappings:
loop_test:
cmp $10, -8(%rbp) # reversed for cmp
jl loop_done # reversed twice
add $1, -8(%rbp)
j loop_test
loop_done:
for (int i = 0; i < 10 && x != 7; ++i) {
x = x + 3;
}
Variable mappings:
# for var init
# i = 0
mov $0, %rcx
for_test:
# %r8 = i < 10
cmp $10, %rcx
setle %r8
# %r9 = x != 7
cmp $7, -8(%rbp)
setne %r9
# %r10 = full cond
mov %r10, %r9
and %r8, %r10 # bitwise and (&) of single bits is logical and (&&)
# for condition
cmp $0, %r10
je for_done
# loop body
# {
add $3, -8(%rbp)
# }
# for increment
inc %rcx
jmp for_test
for_done:
...
int main(...)
{
long x = read_int();
long y = read_int();
long z = foo(x, y);
print_int(z);
exit(0);
}
void print_int(long k)
{
printf("Your number is: %ld\n", k);
}
long read_int()
{
long y;
printf("Type in a number:\n");
scanf("%ld", &y);
return y;
}
long foo(long a, long b)
{
b = bar(b + 1);
b = bar(b + 1);
return 2 * a + b + 3;
}
long bar(int x) {
if (x < 10) {
return x;
}
else {
return x % 20;
}
}
To write this in assembly, we go one function at a time.
Signature and pseudocode:
long bar(int x) {
if (x < 10) {
return x;
}
else {
return x % 20;
}
}
Variable mappings:
Skeleton:
bar:
enter $0, $0
...
leave
ret
Write the body:
bar:
enter $0, $0
cmp $10, %rdi
bge bar_then
jmp bar_else
bar_then:
mov %rdi, %rax
jmp bar_done
bar_else:
mov %rdi, %rax
mov $0, %edx
mov $20, %r10
idiv %r10
mov %rdx, %rax
bar_done:
leave
ret
Signature and pseudocode:
long foo(long a, long b)
{
b = bar(b + 1);
b = bar(b + 1);
return 2 * a + b + 3;
}
Variable mappings:
The arguments a and b are needed after a function call, so we copy them to a callee-save register. These could also be put on the stack. Since we need the argument values after two function calls, a caller-save pattern would be less effective.
Skeleton:
foo:
push %r14
push %r15
enter $0, $0
...
leave
pop %r15
pop %r14
ret
Write the body:
foo:
push %r14
push %r15
enter $0, $0
# move the arguments to callee-save registers
mov %rdi, %r14
mov %rsi, %r15
# b = bar(b + 1)
mov %r15, %rdi
inc %rdi
call bar
mov %rax, %r15
# b = bar(b + 1)
mov %r15, %rdi
inc %rdi
call bar
mov %rax, %r15
# 2 * a + b + 3
mov %r14, %rax
add %rax, %rax
add %r15, %rax
add $3, %rax
leave
pop %r15
pop %r14
ret
Signature and pseudocode:
long read_int()
{
long y;
printf("Type in a number:\n");
scanf("%ld", &y);
return y;
}
Variable mappings:
Note that y must be on the stack, since we're taking its address for scanf.
Skeleton:
read_int:
enter $16, $0 # Align stack & allocate 1 local
...
leave
ret
Body:
read_int:
enter $16, $0 # Align stack & allocates an 16-byte (2-slot) stack frame
mov $read_int_prompt, %rdi
mov $0, %al # required for vararg functions like printf
call printf
mov $read_int_format, %rdi
lea -8($rbp), %rsi
mov $0, %al
call scanf
mov -8($rbp), $rax
leave
ret
...
.data
read_int_prompt:
.string "Type in a number:\n"
read_int_format:
.string "%d"
void print_int(long k)
{
printf("Your number is: %ld\n", k);
}
Variable mappings:
Skeleton:
print_int:
enter $0, $0
...
leave
ret
Body:
print_int:
enter $0, $0
mov %rdi, %rsi
mov $print_int_format, %rdi
mov $0, %al # required for vararg functions like printf
call printf
leave
ret
...
.data
print_int_format:
.string "Your number is: %d\n"
Signature & Pseudocode:
int main(...)
{
long x = read_int();
long y = read_int();
long z = foo(x, y);
print_int(z);
exit(0);
}
Variable mappings:
Skeleton
main:
enter $32, $0 # Allocate stack frame with 3 slots + 1 for alignment
...
leave
ret
Body:
main:
enter $32, $0 # Allocate stack frame with 3 slots + 1 for alignment
call read_int
mov %rax, -8(%rbp)
call read_int
mov %rax, -16(%rbp)
mov -8(%rbp), %rdi
mov -16(%rbp), %rsi
call foo
mov %rax, -24(%rbp)
mov -24(%rbp), %rdi
call print_int
mov $60, %rax
syscall
leave
ret