Course Homepage

Code Generation II (codegen2) Project

Overview

In this project you implement Slang compiler support for pointer operations.

Setup

Run the following commands to download the starter code for this project, and to set up your git repository for project submission:

mkdir -p ~/cop3402fal26/
wget https://www.cs.ucf.edu/~pappas/teaching/cop3402/fal2026/files/codegen2-starter-code.tar
tar -xvf codegen2-starter-code.tar -C ~/cop3402fal26/
cd ~/cop3402fal26/codegen2
git init
git add .
git commit
# Enter a commit message in the editor that pops up
git remote add submission gitolite3@eustis3.eecs.ucf.edu:cop3402/$USER/codegen2
git push --set-upstream submission master

Starter code structure

The starter code for this project has the same structure as that of codegen1.

All starter code files must be added, committed, and pushed with git. Do not add to source control any generated or binary files (e.g., .o files, or the bin/slang binary itself).

You do not need to read all these files in order to complete this project. The only file you will be modifying is src/codegen.c. The project requirements section explains what changes to make to this file.

Building your project

Run the following command to build your compiler (tested on eustis):

cd ~/cop3402fal2026/codegen2
make

The result of the above command will be the bin/slang executable file. This is the executable for the Slang compiler.

Project requirements

For this and all other codegen assignments, you will complete the assignment by implementing a few specific functions in the file src/codegen.c that are labeled with comments that say TODO. Other functions will either be given to you, or hard-coded to work for this specific assignment’s test cases. You should only modify functions labeled with TODO comments, and should not modify any given or hard-coded functions.

Here is a listing of all the functions in this assignment, and whether they are given to you, hard-coded, or are a function you must implement yourself:

Function Category
visit_reference() Implement yourself
visit_assignment_dereference() Implement yourself
visit_dereference_assignment() Implement yourself
visit_arithmetic() Hard-coded (leave blank)
visit_label() Hard-coded (leave blank)
visit_goto() Hard-coded (leave blank)
visit_if() Hard-coded (leave blank)
visit_parameters() Hard-coded (leave blank)
visit_locals() Hard-coded
visit_return() Hard-coded
visit_call() Hard-coded
codegen_err() Given
value_to_string() Given
visit_assignment() Given
visit_statement() Given
visit_body() Given
visit_function() Given
visit_program() Given
visit() Given

The next subsections describe the functions you must implement yourself.

visit_reference()

This function should emit assembly code that retrieves the address of a given source variable, and then stores that address in a destination variable.

Here are some examples (assume a has an offset of -16 and b has an offset of -24):

Slang code Assembly code
a = &b
mov %rbp, %rax
add $-24, %rax
mov %rax, -16(%rbp)
b = &a
mov %rbp, %rax
add $-16, %rax
mov %rax, -24(%rbp)

Here is the relevant snippet from the grammar.ebnf file defining the grammar rule for a Slang reference statement:

reference = identifier '=' '&' identifier

A Slang reference statement consists of an identifier, followed by an equals sign (=), followed by an ampersand (&), followed by an identifier.

Here is the definition of the Reference struct you defined in include/parse.h while completing the slang-format exercise; this struct has all the fields you need to implement visit_reference():

typedef struct reference {
    Identifier  left_hand_side;
    Identifier  right_hand_side;
} Reference;

To compile Slang reference statements to assembly code, modify the function visit_reference() to emit assembly code that does the following steps:

  1. Loads the address of the %rbp register into the %rax register.
  2. Adds the source variable’s stack frame offset to the %rax register.
  3. Stores the value in %rax into the destination variable.

By adding the source variable’s offset to address of the stack frame (i.e., the value stored in %rbp), we can obtain the address of the source variable, even though this specific address may not known be known to us at run-time.

Loading address of %rbp into %rax

To load the value of %rbp into %rax, simply use a mov instruction. When emitting the mov instruction, remember that the left operand is the source, and the right operand is the destination.

Adding the source variable’s offset to %rax

To add the source variable’s offset to %rax, first obtain the offset using the provided offset_for_name() function (defined in src/symbol_table.c). For instance, to get the offset for a variable with name name, you would use:

int offset = offset_for_name(name);

In the case of compiling reference statements, the source variable’s name is stored in reference.right_hand_side.text.

After obtaining the source variable’s offset, next emit an add instruction to add this offset to the %rax register. For instance, to add the offset -16 to %rax, you would emit:

add $-16, %rax

Storing the result in the destination variable

Finally, store the value in %rax into the destination variable. Pass the destinations variable’s name to the given function offset_for_name() to obtain the offset of the destination variable, then emit assembly code that use a mov instruction to store the value in %rax into the memory address at the destination variable’s offset from the base pointer (%rbp). In the case of reference statements, the name of the destination variable is in reference.left_hand_side.text.

For instance, assuming the variable x has a stack frame offset of -16, you would emit the following assembly code to store the contents of the %rax register into x:

mov %rax, -16(%rbp)

For an example of some code that uses the offset_for_name() function, see the definition of the given function visit_assignment().

visit_assignment_dereference()

This function should emit assembly code that retrieves the address stored in a given variable, dereferences it to obtain the value at the stored address, and then stores the resulting value in a destination variable.

Here are some examples (assume a has an offset of -16 and b has an offset of -24):

Slang code Assembly code
a = *b
mov -24(%rbp), %rax
mov (%rax), %rbx
mov %rbx, -16(%rbp)
b = *a
mov -16(%rbp), %rax
mov (%rax), %rbx
mov %rbx, -24(%rbp)

Here is the relevant snippet from the grammar.ebnf file defining the grammar rule for a Slang assignment dereference statement:

assignment_dereference = identifier '=' '*' identifier

A Slang assignment dereference statement consists of an identifier, followed by an equals sign (=), followed by an asterisk (*), followed by an identifier.

Here is the definition of the Assignment_Dereference struct you defined in include/parse.h while completing the slang-format exercise; this struct has all the fields you need to implement visit_assignment_dereference():

typedef struct assignment_dereference {
    Identifier  left_hand_side;
    Identifier  right_hand_side;
} Assignment_Dereference;

To compile Slang assignment dereference statements to assembly code, modify the function visit_assignment_dereference() to emit assembly code that does the following steps:

  1. Loads the address stored in the source variable into %rax.
  2. Dereferences that address, and stores the result into %rbx.
  3. Stores the value in %rbx into the destination variable.

Loading the source variable’s stored address into %rax

To load the address stored in the source variable into the %rax register, first pass the source variable’s name to the given function offset_for_name() to obtain the source variable’s stack frame offset. Then emit assembly code that uses a mov instruction to move the value at this offset from %rbp into the %rax register. In the case of an assignment dereference statement, the name of the source variable is in assignment_dereference.right_hand_side.text.

For instance, assuming the variable x has a stack frame offset of -16, you would emit the following assembly code to store the value stored in x into the %rax register:

mov -16(%rbp), %rax

Dereferencing the address in %rax

To dereference the value in %rax, wrap the operand in parentheses, i.e., use (%rax). Use this syntax to emit a mov instruction that dereferences (%rax), and stores the result into the %rbx register. The assembly code you emit here should only be a single line of code. Remember that the first operand to mov is the source operand, and the second operand is the destination.

Storing the result into the destination variable

See the description of visit_reference() for instructions on how to copy the value in %rbx into the destination variable. Note that in this case, the source operand for the move instruction is %rbx, not %rax.

visit_dereference_assignment()

This function should emit assembly code that retrieves the address stored in a destination variable, and then stores a given value at the memory location stored in the destination variable (not in the destination variable itself).

Here are some examples (assume a has an offset of -16 and b has an offset of -24):

Slang code Assembly code
*a = b
mov -16(%rbp), %rax
mov -24(%rbp), %rbx
mov %rbx, (%rax)
*b = a
mov -24(%rbp), %rax
mov -16(%rbp), %rbx
mov %rbx, (%rax)

Here is the relevant snippet from the grammar.ebnf file defining the grammar rule for a Slang dereference assignment statement:

dereference_assignment = '*' identifier '=' value

A Slang dereference assignment statement consists of an asterisk (*), followed by an equals sign (=), followed by value (which is either an integer or a variable).

Here is the definition of the Dereference_Assignment struct you defined in include/parse.h while completing the slang-format exercise; this struct has all the fields you need to implement visit_dereference_assignment():

typedef struct dereference_assignment {
    Identifier  left_hand_side;
    Value       right_hand_side;
} Dereference_Assignment;

To compile Slang dereference assignment statements to assembly code, modify the function visit_dereference_assignment() to emit assembly code that does the following steps:

  1. Loads the address stored in the destination variable into %rax.
  2. Loads the value to be stored at the address into %rbx.
  3. Stores the value in %rbx at the memory address in %rax.

Loading the destination variable’s stored address into %rax

See the description of visit_assignment_dereference() for instructions on how to and load address stored in a variable into the %rax register. Note that in this case, the name of the variable you are obtaining the stored address of is in dereference_statement.left_hand_side.text.

Loading the source value into %rbx

To load the source value into the %rbx register, emit (i.e., printf()) assembly code that uses a mov instruction to load the source value into the %rax register. To get the assembly code for the source value, pass dereference_assignment.right_hand_side to the given function value_to_string(). For instance, you can use the following code snippet to get the assembly code for the source value.

char *value = value_to_string(dereference_assignment.right_hand_side);

Given a Slang value, value_to_string() returns a null-terminated string containing its assembly representation. For instance, the value 2 would result in "$2", and a variable a with offset -16 from the stack frame would result in "-16(%rbp)". The result of value_to_string() must be freed after you are done using it, e.g.:

free(value);

For more examples of how to use value_to_string(), check the definition of the given function visit_assignment().

After getting the assembly code for the source value, emit a mov instruction that stores the value into the %rbx register.

Storing the value in %rbx at the destination address

Emit a mov instruction that stores the value in %rbx into the memory address stored in %rax. To move a value into a memory address stored in a register, and not into the register itself, wrap the target register’s name in parentheses, e.g., (%rax). For instance, the following assembly code stores the value 1 at the memory address stored in %rax:

mov $1, (%rax)

Note that in the case of visit_dereference_assignment(), the source operand should not be $1, but rather the register %rbx.

Running your compiler

Follow the instructions in codegen1 to run your compiler on the given template file, and to modify the template file to ensure your project fulfills the project requirements.

Running your compiled code

Follow the instructions in codegen1 to assemble and link your compiled Slang code with the provided iolib.c file, and to run the resulting executable.

Self-check

The makefile included in the starter code provides a make test rule you can use to quickly test that your compiler correctly implements all required functionality for this assignment. You can run this rule as follows:

make test

If your code works correctly, then make test should print the name of all this project’s odd-numbered test cases, each followed by the word “success”. Note that the grading server will test your code against the even-numbered test cases as well, which are not given to you, but are described below. You should test that your compiler passes all even-numbered test cases by either writing your own tests, or by running your compiler on sample programs made by modifying the given template file, template.sg.

After using make test to verify that your compiler passes all odd-numbered test cases (and hopefully using your own tests to verify that it passes all even-numbered test cases as well), you should absolutely also check that you submitted your code correctly via git by running make test from within a fresh clone of your repo. You can do this with the following command:

rm -fr ~/tmp/codegen2_selfcheck
git clone gitolite3@eustis3.eecs.ucf.edu:cop3402/$USER/codegen2 ~/tmp/codegen2_selfcheck
cd ~/tmp/codegen2_selfcheck
make test

If your code was pushed successfully, then your fresh clone should pass all tests as well.

Test cases

The test cases used to grade the project are listed below. You can run make test to test your code against the odd-numbered test cases, which are provided publicly. The even-numbered test cases are private, and you should write your own tests for them.

All test cases test your compiler on code based on the provided template file, template.sg. To ensure that your code works with this template, be sure to follow the project requirements, and only modify functions labeled with TODO comments.

# Short Name Description
1 ref-correct-address Use gdb to check that address of a is 16 bytes away from %rbp
2 ref-not-zero Check that the reference address is not 0
3 ref-deref Take a reference and print via dereference
4 ref-deref-change Take a reference and change the referenced variable
5 ref-deref-copy Take a reference and copy its value via the ref
6 ref-assignderef Take a reference and do a dereference assignment
7 ref-deref-assignderef Do all three pointer operations

Grading schema

Criterion Points
The git repo exists 1
The program is written, builds, and runs as described here 1
Tests pass, prorated by the number of test cases passing 6
TOTAL 8