Labs 08: Code Generation
FIRST STEP: Familiarize yourself with Cafebabe by using it to directly write example class files and then running them.
Congratulations, your front-end is complete! You are now one step away from having written a complete compiler. This week's lab description and stubs are rather short. It's not that we don't want to help you anymore, it's just that the tasks should be straightforward (and there's a bit of reading to do on the Cafebabe website).
Generating class files is a tedious and not so interesting task. Therefore you will use a library for this. Please read the Cafebabe documentation pages.
Concatenating and printing strings
To concatenate and print out strings, you will need to call methods from the Java library (as there are no bytecodes to do that directly). You only need to handle the cases where you print booleans, integers or strings1). Turns out that System.out.println(…) of the Java library is conveniently overloaded for all these cases.
To call System.out.println(…), you need to invoke the proper method println(…) (with the signature matching what you're trying to print, that is) on the static object System.out, so you need to emit a GETSTATIC bytecode, then emit the code for the expression you're trying to print, then emit an INVOKEVIRTUAL bytecode. System.out is of type java.io.PrintStream.
Concatenation is done using java.lang.StringBuilder. The procedure consists in building a new instance of a StringBuilder, then appending to it whatever you want to concatenate together, then calling toString() on the builder. The append method is overloaded for strings and integers, and you're not asked to be able to concatenate any other type.
If in doubt, compile an example with our reference compiler and run
javap -c on the
.class file to see what we did.
We will handle equality in a simple way:
- integers and booleans are compared by value
- other types (strings, arrays and objects) are compared by reference. In the case of strings, the result may or may not be the same as calling .equals(…), depending on whether the strings are constant. (In other words, don't do anything special for equality.)
You have to apply lazy evaluation to boolean expressions (short-circuit). Make sure your compiled code for expressions such as (true || (1/0 == 1)) doesn't crash.
Notes on types in the JVM
The various naming conventions in the JVM can be confusing: some bytecodes contain a letter indicating to which type of operands they apply (eg. ILOAD which loads integers, IF_ACMPEQ which compares two references for equality or LRETURN which returns a
long value). In this convention, we have:
|A||Reference (object or array)|
…but then there is another convention when we want to describe fields or methods types and methods signatures (see this page). In particular, note that L is used for objects (but not arrays) in that second convention, but for
long operands in bytecodes.
Finally, note that returning from a
void method is done using the RETURN bytecode (no prefix letter).
Complete the stub for CodeGenerator.scala such that your compiler emits class files. You should be able to run the main class with
java and get the same result as with our reference compiler for valid Tool programs. If you think the reference compiler is doing something wrong, let us know.
Your compiler should generate class files (one per class, as in Java, and one for the main object) silently if the compilation is successful, or generate errors and warnings from the previous phases in case there was a problem. The code generation phase should not produce any error, and the generated class files should be executable on the Java Virtual Machine. You should store all the relevant meta information in the class files: line numbers and source file identification (see the cafebabe doc).
As usual, you can download the stubs from our main repository by following the instructions.
- Main.scala The main program. It now accepts a single argument
-d outputdirto specify the output directory for the class files. The arguments parsing is done in
Main, and the handling is done in
- Compiler.scala Note that this week you have to complete this file (generation of the main class).
- code/CodeGenerator.scala Stub for the code generator.
…and you've seen this one before too:
toolc ├── Compiler.scala (stub given this week) ├── Main.scala (given this week) ├── Positional.scala (given in the first Tool lab) ├── Reporter.scala (given in the first Tool lab) ├── TreePrinter.scala (completed in the Analyzer lab) │ ├── analyzer │ ├── Analyzer.scala (completed in the Type Checker lab) │ ├── Symbols.scala (completed in the Type Checker lab) │ ├── TypeChecker.scala (completed in the Type Checker lab) │ └── Types.scala (completed in the Type Checker lab) │ ├── code │ └── CodeGenerator.scala (stub given this week) │ ├── lexer │ ├── Lexer.scala (completed in the Parser lab) │ └── Tokens.scala (completed in the Parser lab) │ └── parser ├── Parser.scala (completed in the Parser lab) └── Trees.scala (completed in the Type Checker lab)
As usual, please choose a commit from your git repository as a deliverable on our server before Tuesday, Dec. 4th, 11.59pm (23h59). We will scan your repository for a directory called src/toolc and compile all the .scala files below it.
- The complete JVM specification (may be hard to digest)