A compiler for MiniJava, a subset of Java, that produces LLVM IR. The generated IR is compiled to a native executable with clang. Built for the Compilers course at the Department of Informatics and Telecommunications, University of Athens.
- Parsing – a JavaCC parser, with the syntax tree and visitor interfaces generated by JTB, parses the source file and reports syntax errors.
- Symbol table – a first visitor (
MyVisitor) collects classes, fields, methods and variables, computes field and vtable offsets, and rejects duplicate declarations. - Type checking – a second visitor (
CheckingVisitor) checks every statement and expression: type compatibility with inheritance, method overriding rules, argument types, return types and more. - Code generation – a third visitor (
LLVMVisitor) emits LLVM IR: objects with vtables for dynamic dispatch, heap-allocated arrays with runtime bounds checks, and short-circuit&&.
The compiler exits with a non-zero status and a descriptive message on the first error.
class Factorial {
public static void main(String[] a) {
System.out.println(new Fac().compute(5));
}
}
class Fac {
public int compute(int n) {
int result;
if (n < 1)
result = 1;
else
result = n * (this.compute(n - 1));
return result;
}
}The recursive call becomes a virtual call through the object's vtable:
define i32 @Fac.compute(i8* %this, i32 %.n) {
...
if1:
%_11 = load i32, i32* %n
%_12 = bitcast i8* %this to i8***
%_13 = load i8**, i8*** %_12
%_14 = getelementptr i8*, i8** %_13, i32 0
%_15 = load i8*, i8** %_14
%_16 = bitcast i8* %_15 to i32(i8*, i32)*
%_18 = load i32, i32* %n
%_19 = sub i32 %_18, 1
%_17 = call i32 %_16(i8* %this, i32 %_19)
%_20 = mul i32 %_11, %_17
...
}A complete example is in Example.java and its output in Example.ll.
The Docker image contains everything needed (JDK, JavaCC/JTB, clang):
docker build -t minijava .
docker run --rm minijava # run the test suite
# compile and run your own program
docker run --rm -v "$PWD:/src" minijava bash -c \
"java -cp /app Main /src/Program.java && clang -o /tmp/prog /src/Program.ll && /tmp/prog"Without Docker you need a JDK and clang 14 or older (the generated IR uses typed pointers), then:
make compile
java Main Program.java # writes Program.ll
clang -o program Program.ll && ./programrun_tests.sh runs the 87 programs in test/typechecking:
ERROR_*programs must be rejected by the compiler.- Valid programs are compiled to LLVM IR and then to a native binary. Since MiniJava is a subset of Java, each
one is also compiled with
javacand run on the JVM, and the two outputs must match, including programs that end with an out-of-bounds array access.
The suite runs on every push via GitHub Actions.
Java, JavaCC, JTB, LLVM IR, clang, Docker, GitHub Actions