Skip to content

Latest commit

 

History

11 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Mini-Java-Compiler

tests

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.

How it works

  1. Parsing – a JavaCC parser, with the syntax tree and visitor interfaces generated by JTB, parses the source file and reports syntax errors.
  2. Symbol table – a first visitor (MyVisitor) collects classes, fields, methods and variables, computes field and vtable offsets, and rejects duplicate declarations.
  3. Type checking – a second visitor (CheckingVisitor) checks every statement and expression: type compatibility with inheritance, method overriding rules, argument types, return types and more.
  4. 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.

Example

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.

Running it

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 && ./program

Tests

run_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 javac and 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.

Technologies

Java, JavaCC, JTB, LLVM IR, clang, Docker, GitHub Actions

About

Compiler for MiniJava (a Java subset) to LLVM IR, with type checking, vtable dispatch and a JVM-verified test suite

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages