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;
, -8(%rbp)
mov $5
# int b = 3 * a + 1;
-8(%rbp), %r11
mov , %r11
imulq $3%r11
inc %r11, -16(%rbp) mov
// Case 1
if (x < y) {
= 7;
y }
// Case 2
if (x < y) {
= 7;
y }
else {
= 9;
y }
Variable Mapping:
Case 1:
# if (x < y)
-16(%rbp), %r10 # cmp can only take one indirect arg
mov %r10, -8(%rbp) # cmp order backwards from C
cmp else1: # condition reversed, skip block unless cond
jge
# y = 7
, -16(%rbp) # need suffix to set size of "7"
movq $7
else1:
...
Case 2:
# if (x < y)
-16(%rbp), %r10 # cmp can only take one indirect arg
mov %r10, -8(%rbp) # cmp order backwards
cmp else1: # condition reversed, skip block unless cond
jge
# then {
# y = 7
, -16(%rbp) # need suffix to set size of "7"
movq $7
# skip else
j done1
# } else {
else1:
# y = 9
, -16(%rbp)
movq $9
# }
done1:
...
do {
= x + 1;
x } while (x < 10);
Variable Mapping:
loop:
, -8(%rbp)
add $1
, -8(%rbp) # reversed for cmp arg order
cmp $10# sense reversed
jl loop
# ...
while (x < 10) {
= x + 1;
x }
Variable mappings:
loop_test:
, -8(%rbp) # reversed for cmp
cmp $10# reversed twice
jge loop_done
, -8(%rbp)
add $1
jmp loop_test
loop_done:
for (int i = 0; i < 10 && x != 7; ++i) {
= x + 3;
x }
Variable mappings:
# for var init
# i = 0
, %rcx
mov $0
for_test:
# %r8 = i < 10
, %rcx
cmp $10%r8
setle
# %r9 = x != 7
, -8(%rbp)
cmp $7%r9
setne
# %r10 = full cond
%r10, %r9
mov %r8, %r10 # bitwise and (&) of single bits is logical and (&&)
and
# for condition
, %r10
cmp $0
je for_done
# loop body
# {
, -8(%rbp)
add $3
# }
# for increment
%rcx
inc
jmp for_test
for_done:
...
int main(...)
{
long x = read_int();
long y = read_int();
long z = foo(x, y);
(z);
print_int(0);
exit}
void print_int(long k)
{
("Your number is: %ld\n", k);
printf}
long read_int()
{
long y;
("Type in a number:\n");
printf("%ld", &y);
scanfreturn y;
}
long foo(long a, long b)
{
= bar(b + 1);
b = bar(b + 1);
b 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:
, $0
enter $0
...
leave ret
Write the body:
bar:
, $0
enter $0
, %rdi
cmp $10
bge bar_then
jmp bar_else
bar_then:
%rdi, %rax
mov
jmp bar_done
bar_else:
%rdi, %rax
mov , %edx
mov $0, %r10
mov $20%r10
idiv %rdx, %rax
mov
bar_done:
leave ret
Signature and pseudocode:
long foo(long a, long b)
{
= bar(b + 1);
b = bar(b + 1);
b 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:
%r14
push %r15
push , $0
enter $0
...
leave%r15
pop %r14
pop ret
Write the body:
foo:
%r14
push %r15
push , $0
enter $0
# move the arguments to callee-save registers
%rdi, %r14
mov %rsi, %r15
mov
# b = bar(b + 1)
%r15, %rdi
mov %rdi
inc
call bar%rax, %r15
mov
# b = bar(b + 1)
%r15, %rdi
mov %rdi
inc
call bar%rax, %r15
mov
# 2 * a + b + 3
%r14, %rax
mov %rax, %rax
add %r15, %rax
add , %rax
add $3
leave%r15
pop %r14
pop ret
Signature and pseudocode:
long read_int()
{
long y;
("Type in a number:\n");
printf("%ld", &y);
scanfreturn y;
}
Variable mappings:
Note that y must be on the stack, since we’re taking its address for scanf.
Skeleton:
read_int:
, $0 # Align stack & allocate 1 local
enter $16
...
leave ret
Body:
:
read_int16, $0 # Align stack & allocates an 16-byte (2-slot) stack frame
enter $
, %rdi
mov $read_int_prompt0, %al # required for vararg functions like printf
mov $
call printf
, %rdi
mov $read_int_format-8($rbp), %rsi
lea 0, %al
mov $
call scanf
-8($rbp), $rax
mov
leave
ret
...
.data
:
read_int_prompt.string "Type in a number:\n"
:
read_int_format.string "%d"
void print_int(long k)
{
("Your number is: %ld\n", k);
printf}
Variable mappings:
Skeleton:
print_int:
, $0
enter $0
...
leave ret
Body:
print_int:
, $0
enter $0
%rdi, %rsi
mov , %rdi
mov $print_int_format, %al # required for vararg functions like printf
mov $0
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);
(z);
print_int(0);
exit}
Variable mappings:
Skeleton
main:
, $0 # Allocate stack frame with 3 slots + 1 for alignment
enter $32
...
leave ret
Body:
main:
, $0 # Allocate stack frame with 3 slots + 1 for alignment
enter $32
call read_int%rax, -8(%rbp)
mov
call read_int%rax, -16(%rbp)
mov
-8(%rbp), %rdi
mov -16(%rbp), %rsi
mov
call foo%rax, -24(%rbp)
mov
-24(%rbp), %rdi
mov
call print_int
, %rax
mov $60
syscall
leave ret