CS 202 - Notes 2018-10-19
x86-64 Assembly: Control Flow
Control Flow
Control flow is governed by four control flags in the processor
CF - carry flag :This is set when the operation has a carry out of the last bit. It indicates an overflow if the numbers were unsigned
ZF - zero flag :The most recent operation yielded a 0
SF - sign flag :The result of the most recent operation was negative
OF - overflow flag :Set if there was two’s compliment overflow (positive or negative)
These can be set explicitly or implicitly. They are set implicitly by almost every arithmetic operation we perform. The exception is the lea function, which was meant for address computation
we can set them explicitly using:
cmp
- compare two values (essentially subtracts the first from the second)test
- ANDs the two arguments together. If they are the same, this will be a quick way to check if it is 0 or negative
if statements
C code:
int simpleConditional(int x, int y){
int result;
if (x > y){
result = x - y;
}else{
result = y - x;
}
result *= 3;
return result;
}
Assembly:
cmpl %esi, %edi
jg .L4
subl %edi, %esi
.L3:
leal (%rsi,%rsi,2), %eax
ret
.L4:
subl %esi, %edi
movl %edi, %esi
jmp .L3
do while loops
As we observed in class, the do while loop really is the simplest loop since it requires a single conditional branch at the bottom of the loop to return to the top of the condition is met.
C code:
int doWhile(int x, int y){
int result;
do {
result += x;
y--;
}while (y > 0);
return result;
}
Assembly:
movl $0, %eax
.L2:
addl %edi, %eax
subl $1, %esi
testl %esi, %esi
jg .L2
rep ret
As an interesting aside, if we turn on optimization, the compiler will actually detect what this function is trying to do (multiplication) and remove the loop in favor of the multiplication.
Assembly (-O2)
testl %esi, %esi
movl $1, %eax
cmovg %esi, %eax
imull %edi, %eax
ret
while loops
There is a very little difference between do while and while loops. A do while loop goes: init, body, update, test, body, update, test, body, update, test, etc... A while loop goes: init, test, body, update, test, body, update, test, body, update, etc... The difference is that the while loop does a test before it starts, or to put that another way, the do while does an iteration of body and update before it starts.
There are a couple of ways that we can translate a while loop into a do while loop.
jump to the middle : We start by unconditionally jumping down to the the test
guarded do : We just add a test before the do while loop that jumps over the loop
Different levels of optimization or different loops may result in one or the other of these approaches, but we will largely see the first.
C code:
int whileLoop(int x, int y){
int result;
while (y > 0) {
result += x;
y--;
}
return result;
}
Assembly :
movl $0, %eax
jmp .L2
.L3:
addl %edi, %eax
subl $1, %esi
.L2:
testl %esi, %esi
jg .L3
rep ret
Comparing this to the do while loop, you will see that the only difference is the addition of a single line (jmp .L2
).
for loops
This is basically pretty syntax for the same behavior as a while loop, so it can be converted the same way.
for (init; test; update){
body;
}
init;
while(test){
body;
update;
}
C code:
int forLoop(int x, int y){
int result = 0;
for (; y > 0; y--){
result +=x;
}
return result;
}
Assembly :
movl $0, %eax
jmp .L2
.L3:
addl %edi, %eax
subl $1, %esi
.L2:
testl %esi, %esi
jg .L3
rep ret
If you compare that to the assembly for the while loop, you will see it is identical.
Supporting Procedures
We started to look at procedures today.
Basic concerns:
Data management
- need to be able to pass arguments
- Need to return a value
Control flow
- Need to be able to jump to the procedure
- Need to be able to return to the instruction immediately after the call when we are done
Memory management
- Need to be able to create local variables
- Need local variables to go away when we are done (talk to someone who knows some JavaScript for some more complicated forms of this)
- Local variables of the caller need to be preserved
- Functions must be re-entrant (we need to be able to enter a second time before the first invocation is complete -- see recursion)
We observed that we know how to do some of these things.
Values from functions are return in %rax (or %eax, etc depending on data size)
Parameters are passing into function in registers (%rdi, %rsi, etc...). However, this is only a partial solution since we only have six designated registers.
We saw the
ret
command, but we don't know how it works yet.