This repository contains the starter code for Lab 7 of the course Language and Compiler Design.
In this lab, you will extend the previous lab sessions' work on the compiler of CALCRef language with heap allocated memory locations. This starting point does not yet have let implemented in all its aspects. Please modify your own ongoing project from Lab 3, 4 or 5 with the following new features.
-
new(E)to allocate a new memory location in the heap and initialize it with the value of expressionE. The result ofnew(E)is a reference to the allocated memory location. -
!Eto access the value stored in the memory location referenced by expressionE. -
E1 := E2to update the memory location referenced by expressionE1with the value of expressionE2. The result of this expression is the value assigned to the memory location. -
free(E)to release the memory location referenced by expressionE. -
if E1 then E2 else E3to evaluate the boolean expressionE1, and if it evaluates totrue, evaluate and return the result of expressionE2; otherwise, evaluate and return the result of expressionE3. -
while E1 do E2to repeatedly evaluate expressionE2as long as the boolean expressionE1evaluates totrue. -
E1; E2to evaluate expressionE1, discard its result, and then evaluate and return the result of expressionE2. -
printInt(E)to print the integer value of expressionEto the standard output. -
printBool(E)to print the boolean value of expressionEto the standard output. -
printEndLine()to print a new line.
Note that now we no longer need to print the result in the end of the compiled file or at the end of the interpreter call.
Collect some files from this repository as reference. Your job is to implement declarations of identifiers in the style of let expressions. You need to implement the interpreter, the type systems, and the compiler to LLVM. Use the provided module mem.ml to manage memory locations in the interpreter, and the module mem_runtime.c to manage heap memory in the generated LLVM code.
Assuming that you have completed the previous labs, the following steps outline the tasks you need to accomplish in this lab:
-
Lexer: Modify the lexer (
lexer.mll) to recognize the new constructs for memory allocation, memory access, and memory release. You will need to add tokens fornew,!, and:=. Add also the keywords and symbols required for the imperative constructs;(sequence),if,then,else,while, anddo. Add operationprintIntandprintBoolas well. -
Parser: Update the parser (
parser.mly) to handle the new expressions Ensure that the parser correctly constructs the AST for these expressions. The sequence, conditionals andwhileexpressions have similar precedence asletexpressions. Assignments have higher than let expressions but have a lower precedence when compared with the other existing operations. Sequences are left associative, Assignments and all the others are right associative. -
AST: Extend the AST definition (
ast.ml) to include the new constructors. -
Type System: Implement the type checking for let expressions in the type system module (
typing.ml). Follow the rules presented in the lecture slides. Add the typeUnitand valueunit. -
Interpreter: Implement the evaluation of let expressions in the interpreter module (
eval.ml). Use the functions in modulemem.mlto allocate memory locations for the identifiers declared in the let expressions. -
LLVM Code Generation: Extend the LLVM code generation module (
llvm.ml) to handle imperative constructs expressions. This involves generating LLVM code that uses the functions in modulemem_runtime.cand compiling the result together.
Building the project follows the same steps as in previous labs. Make sure you have dune installed.
Run the following command inside the project root:
dune buildThis compiles the interpreter and related modules.
After building, you can run the interpreter with:
dune exec calccThis will start the program that evaluates expressions written in the defined expression language.
The program asks for an expression to compile and outputs some LLVM code. For instance, if the expression is 1+2+3, the output will be
@.str = private unnamed_addr constant [4 x i8] c"%d\0A\00", align 1
define i32 @main() #0 {
%1 = add nsw i32 1, 2
%2 = add nsw i32 %1, 3
%3 = call i32 (ptr, ...) @printf(ptr noundef @.str, i32 noundef %2)
ret i32 0
}
declare i32 @printf(ptr noundef, ...) #1This output should be placed in a file with the extension ll and compiled using clang. When using Unic, from the command line, you can redirect the output to a file using a > operator.
clang -o a a.llThe result is the executable a which can be run from the console
./ato obtain the result 6
To research LLVM instructions, you can read the documentation online and can compile sample C programs to LLVM. The command to do so is
clang -S -emit-llvm -c a.c -o a.llthe result will be a file called a.ll