CS 208 s22 — x86-64 Control Flow: Loops and Switch Statements
Table of Contents
1 Review
Take some time to practice translating assembly to C. There's no single correct C code for the given assembly code—multiple valid translations exist.
Exercise 1: long f(long x, long y)
1
f(long, long): subq %rsi, %rdi jne .L3 movq %rdi, %rax ret .L3: movq %rsi, %rax ret
Exercise 2: long f(long x, long y)
2
f(long, long): cmpq $2, %rdi setle %dl cmpq %rsi, %rdi sete %al testb %al, %dl je .L3 movl $1, %eax ret .L3: movl $2, %eax ret
Exercise 3: long f(long *p)
3
f(long *p): testq %rdi, %rdi je .L4 movq (%rdi), %rax leaq -10(%rax), %rdx testq %rdx, %rdx jle .L3 addq %rax, %rax ret .L3: addq $1, %rax ret .L4: movl $0, %eax ret
2 Loops
2.1 Example
int sum_x_to_y(int x, int y) { int sum = 0; while (x < y) { sum += x; x++; } return sum; }
Loops don't require anything special beyond what we've already seen for conditionals: we have loop conditions (usually implemented via cmp
) and jumps to get us back to the top of the loop. Consider the C code above, and fill in the assembly below. %edi
will hold x
and %esi
will hold y
. You will use two addl
, a cmpl
, a jl
, a jmp
, and a movl
.4
sum_x_to_y: // initialize sum loop_body: loop_test: ret
Just like conditionals, we can start the journey from C to assembly by translating loops to a form using explicit goto statements to show the necessary jumps.
2.2 do-while loops
do <body-statement> while (<test-expr>);
becomes
loop: <body-statement> t = <test-expr> if (t) goto loop;
2.3 while loops
while (<test-expr>)
<body-statement>
becomes
goto test; loop: <body-statement> test: t = <test-expr> if (t) goto loop;
- note the jump-to-the-middle approach to loop implementation
- On higher optimization settings, while loops get translated into a guarded-do form
- a conditional branch followed by a do-while loop
- can allow the initial check to be optimized away if the compiler can determine the condition will always hold
- here's a C function that takes a number and uses a while loop to compute the factorial of it:
long fact_while(long x){ long result = 1; while (x > 1) { result = result * x; x = x - 1; } return result; }
This function compiles to the following assembly (gcc version 7, -Og
flag). It follows the jump-to-the-middle approach.
fact_while: movl $1, %eax jmp .L2 .L3: imulq %rdi, %rax subq $1, %rdi .L2: cmpq $1, %rdi jg .L3 rep ret
If we increase the optimization to -O2
, we can see how it changes to a guarded-do (initial comparison either jumps over the loop or proceeds directly into the loop body).
fact_while: cmpq $1, %rdi movl $1, %eax jle .L4 .L3: imulq %rdi, %rax subq $1, %rdi cmpq $1, %rdi jne .L3 rep ret .L4: rep ret
2.4 for loops
- C language standard states that in the absence of
continue
for (<init-expr>; <test-expr>; <update-expr>)
<body-statement>
is equivalent to
<init-expr>;
while (<test-expr>) {
<body-statement>
<update-expr>;
}
- hence, a for loop is then translated using jump-to-the-middle or guarded-do depending on the optimization level
- changing the while loop to a for loop in our factorial function doesn't change the assembly at all:
long fact_for(long x){ long result = 1; for(long i = x; i > 1; i--) { result = result * i; } return result; }
compiles to (gcc version 7, -Og
flag)
fact_for: movl $1, %eax jmp .L2 .L3: imulq %rdi, %rax subq $1, %rdi .L2: cmpq $1, %rdi jg .L3 rep ret
3 Switch
- using a switch statement can make code more readable than a long chain of if/else if/else.
- also allows for efficient implementation via a jump table
- array where entry \(i\) is the address of the code to be executed when the switch value is \(i\)
- because it's handled with a single array lookup and jump, the time to perform switch is constant regardless of the number of cases (as opposed to series of if-else)
- best used when there are more than a few cases and where the values are not too far apart
- see CSPP figures 3.22 and 3.23 (p. 234-5) for example implementation
long switch_ex(long x, long y, long z) { long w = 1; switch(x) { case 1: w = y * z; break; case 2: w = y + z; case 3: w += z; break; case 5: return y + 7; case 6: w -= z; break; default: w = 2; } return w; }
switch_ex(long, long, long): cmpq $6, %rdi // compare x to 6 ja .L9 // if x > 6, jump to the label for the default case jmp *.L4(,%rdi,8) // otherwise, use x as an offset from L4 to select a label to jump to .L4: .quad .L9 .quad .L8 .quad .L7 .quad .L10 .quad .L9 .quad .L5 .quad .L3 .L8: // x == 1 movq %rsi, %rax imulq %rdx, %rax ret .L7: // x == 2 leaq (%rsi,%rdx), %rax jmp .L6 .L10: // x == 3, move 1 into w since that matters here movl $1, %eax .L6: // x == 3, fall-through from x == 2 addq %rdx, %rax ret .L5: // x == 5 leaq 7(%rsi), %rax ret .L3: // x == 6 movl $1, %eax // move 1 into w since that matters here subq %rdx, %rax ret .L9: // x == 0, x == 4, x > 6, x < 0 movl $2, %eax ret
4 Practice
Translate this assembly to C code:5
.LC0: .string "Hello %d" main: pushq %rbx movl $0, %ebx jmp .L2 .L3: movl %ebx, %esi movl $.LC0, %edi movl $0, %eax call printf addl $1, %ebx .L2: cmpl $9, %ebx jle .L3 movl $0, %eax popq %rbx ret
CSPP practice problems 3.24 (p. 224), 3.26 (p. 228), and 3.30 (p. 236)
Footnotes:
long f(long x, long y) { long z = x - y; if (z == 0) { return z; } return y; }
f(long, long): subq %rsi, %rdi // compute x - y, ok to overwrite x since we don't use it again jne .L3 // jump to L3 when %rdi != %rsi (i.e., x - y != 0) movq %rdi, %rax // copy z to the return value ret .L3: movq %rsi, %rax // copy y to the return value ret
long f(long x, long y) { if (x < 3 && x == y) { return 1; } else { return 2; } }
f(long, long): cmpq $2, %rdi // perform x - 2, set condition codes setle %dl // write 1 to the lowest byte of %rdx when x <= 2 (i.e., %dl contains 1 when x < 3) cmpq %rsi, %rdi // perform x - y, set condition codes sete %al // write 1 to the lowest byte of %rax when x == y testb %al, %dl // perform %al & %dl, set condition codes je .L3 // jump to L3 when %al & %dl == 0 (i.e., jump unless both previous set instructions // wrote 1, meaning x < 3 and x == y) movl $1, %eax // make return value 1 ret .L3: movl $2, %eax // make return value 2 ret
#include <stdlib.h> // to provide NULL long f(long *p) { if (p == NULL) { return 0; } if (*p - 10 > 0) { return *p + *p; } else { return *p + 1; } }
f(long *p): testq %rdi, %rdi // perform p & p, set condition codes je .L4 // jump to L4 if p & p == 0 (i.e, jump if p is NULL) movq (%rdi), %rax // put *p in the return value leaq -10(%rax), %rdx // put *p - 10 in %rdx (remember lea uses the value in the register, not memory testq %rdx, %rdx // perform (*p - 10) & (*p - 10), set condition codes jle .L3 // jump to L3 if (*p - 10) & (*p - 10) <= 0 (i.e., don't jump if *p - 10 > 0) addq %rax, %rax // return value is now *p + *p ret .L3: addq $1, %rax // add 1 to return value ret .L4: movl $0, %eax // set return value to 0 ret
sum_x_to_y: movl $0, %eax jmp .L2 .L3: addl %edi, %eax addl $1, %edi .L2: cmpl %esi, %edi jl .L3 ret
#include <stdio.h> int main() { for (int i = 0; i < 10; i++) { printf("Hello %d", i); } }