CS 377P: Programming for Performance

Assignment 2: Compiler optimizations and x86-64 ISA

Due date: September 20, 2023, 10:00PM

Late submission policy: Submissions can be at most 1 day late. There will be a 10% penalty for late submissions.

Description

The objective of this assignment is to teach you about compiler optimizations and also familiarize you with the x86-64 ISA. We will use the Compiler Explorer tool, which you can access at https://godbolt.org. This tool lets you compile programs in a variety of languages using different compilers, and it shows you the assembly code produced for that program using that compiler.
You should be prepared to spend 10-15 minutes initially playing with the tool to familiarize yourself with it. You will also need to go online to figure out what instructions in the x86-64 ISA do.
One useful reference is https://www.felixcloutier.com/x86/   but you may find it easier to search on the Internet for individual instructions.

Since the x86 ISA can operate on data of different sizes, you should make sure you understand the conventions for specifying data lengths for operands and registers. For example, "rdi" is the name of a 64-bit register, while "edi" refers to the 32 least significant bits of the same register. If this is confusing to you, read the lecture notes and online material to get this clear before you start the assignment.

The assembly code will be different for different compilers, and even for the same compiler, the assembly code will be different in general for different optimization levels, so it is important for you to read the instructions below to select the right compiler and optimization levels for each study. In addition, x86 code can be displayed in AT&T syntax or Intel syntax as we discussed in class. For this assignment, we will use AT&T syntax.

1. Test program: The following C program is available as a sample program in the Compiler Explorer tool. Load that program or type the listing below into code window. In the drop-down menu at the top of the code window, you should select "C" to tell the tool you want to compile a C program.

int testFunction(int* input, int length) {
  int sum = 0;
  for (int i = 0; i < length; ++i) {
    sum += input[i];
  }
  return sum;
}

2. Compiler:   We will use x86-64 gcc 9.2 exclusively for this assignment. At the top of the code window, there is a button labeled "+ Add new". Click on this and it will drop down a menu from which you select "Compiler". A new window for the assembly code will be created and it has a button on top using which you can select the appropriate compiler. It is important to select the right one - we will not grade your assignment if you show us code with a different compiler.

i) At the top of the assembly code window, you will find a check box labeled "Intel". Checking this box will display the assembly code in Intel format. You should uncheck this box since we will work with AT&T syntax, which is easier to understand.

ii) Optimization level: there is a text box at the top of the assembly code window which you can use to pass flags to the compiler. For this assignment, we will study two optimization levels: "-O1" and "-O3". As explained in class, optimization level O1 generates simple but possibly inefficient code whereas optimization level O3 generates more efficient code. For the given test programs, optimization level O3 generates vector instructions where O1 generates scalar instructions.

3. Assembly code with O1: here is the assembly code we obtained. Make sure you see this code before proceeding.

testFunction:
testl %esi, %esi
jle .L4
movq %rdi, %rax
leal -1(%rsi), %edx
leaq 4(%rdi,%rdx,4), %rcx
movl $0, %edx
.L3:
addl (%rax), %edx
addq $4, %rax
cmpq %rcx, %rax
jne .L3
.L1:
movl %edx, %eax
ret
.L4:
movl $0, %edx
jmp .L1

a) This code does not use the stack since there are enough registers in the x86-64 ISA for the parameters and return value to be passed in registers. The standard convention on x86-64 is that the first parameter is passed in register "rdi/edi"; if it is 64-bit value like an address, you can access it in the callee code by reading "rdi" and if it is a 32-bit value like an int, it is passed in the bottom 32 bits of this register and you access it as "edi" in the callee code. The second parameter is passed in register "rsi/esi" by convention and the return value is passed in register "rax/eax".

i) Annotate the assembly language instructions in the code above with comments describing what they do. Here is an example of what we expect.

testFunction:
testl %esi, %esi     ; Test the value of length, which is passed in register esi as a 64-bit value
jle .L4                    ; If this value is less than or equal to zero, jump to L4
movq %rdi, %rax  ; Move the starting address of the array input, passed in register rdi, to register rax
leal -1(%rsi), %edx
leaq 4(%rdi,%rdx,4), %rcx
movl $0, %edx
.....

ii) Write a short paragraph giving the big picture of how this code works. This paragraph can start with the following sentences:

"Parameter input is a 64-bit address, and it is passed in register rdi. Parameter length is a 32-bit int, so it is passed in esi.

The code first checks to see if length is less than or equal to zero. If so, it jumps to L4, where the integer value 0 is written to register edx, and code jumps to L1 where this value is moved to register eax. The procedure then returns. This is correct since the return value should be zero if the array length is not positive.

If the length is positive, ....."

You get the idea.

4. Assembly code with -O3: Repeat this with the optimization level set to -O3. You will find that the generated code is bigger and more complex. Part of your job is to figure how this code works. Here are some hints that will help you.

If you look at the loop in the assembly code, you will see that it uses vectors registers (xmm0 and xmm2)  and vector instructions (paddd). The vector registers are 128 bits long, so each one can store 4 ints. Vectorization is performed by loading 4 elements of the array at a time into register xmm2, and adding these 4 elements to vector register xmm0. Vector register xmm0 keeps a "running sum vector" with four elements so one of these elements will have the value of (input[0]+input[4]+input[8]....), the next one will have the value of (input[1]+input[5]+input[9]+...)  and so on.

Once the loop is done, you need to add up the 4 elements in the running sum vector in register xmm0. The code below the loop does this, and it uses the instruction PSRLDQ, which is described at the end of this assignment.

The code also has to handle the case when the length of the input array is not a multiple of 4. This is handled by the last chunk of code before the returns.

These hints should be enough for you to be able to make sense of the assembly listing, but you will have to look up the meaning of individual instructions.

i) Annotate the assembly language instructions with comments describing what they do.

ii) Write a short narrative giving the big picture of how this code works.

5.  Optimizations: Using the terminology introduced in lecture, explain what optimizations are performed by the compiler with optimization level O3 that are not performed if the optimization level is O1.

What to turn in

Turn in a text file with the annotated assembly listings, the descriptions of the codes, and the answer to (5).