Lab 6 - runtime

In this lab, we will write the runtime so that our compiler can interact with the underlying operating system.

Setup

Retrieve the lab code and commit it to your git with the following commands,

$ mkdir lab6
$ cd lab6
$ curl https://rfc1149.net/tmp/lab6/dragon-tiger.tar.gz | tar zxvf -
$ git add -f dragon-tiger/
$ git commit -m "Import dragon-tiger for lab6"

Now let us build the project,

$ cd dragon-tiger
$ ./configure --with-llvm=/usr/lib/llvm-3.9
$ make

If everything worked as expected, you should be able to run the compiler driver dtiger as follows,

$ src/driver/dtiger --help
Options:
  -h [ --help ]         describe arguments
  --dump-ast            dump the parsed AST
  --dump-ir             dump the generated IR
  -b [ --bind ]         run the binder on the parsed AST
  -t [ --type ]         run the type checker on the parsed AST
  -i [ --irgen ]        run the LLVM IR code generator
  --trace-parser        enable parser traces
  --trace-lexer         enable lexer traces
  -v [ --verbose ]      be verbose
  --input-file arg      input Tiger file

$ echo "print_int(42)" | src/driver/dtiger -i --dump-ir -
; ModuleID = 'tiger'
source_filename = "tiger"

%ft_main = type {}

define i32 @main() {
entry:
  %frame = alloca %ft_main
  br label %body

body:                                             ; preds = %entry
  call void @__print_int(i32 42)
  ret i32 0
}

declare void @__print_int(i32)

Ensure that you test thoroughly and commit each feature. Follow precisely the instructions, this lab is graded and machine corrected!

Important note

Some of you may not have completed the previous assignment. To this effect, the current archive contains pre-built working compiler parts (so as not to give you the full solution) and the runtime may be done separately.

As you may have done in the previous lab, you can reuse your own code by importing the src/parser, src/ast and src/irgen directories and adjusting src/Makefile.am (don’t forget to put parser first in SUBDIRS) and configure.ac.

You might also want to propagate your evaluator code although this will not be needed.

What is the runtime?

You might remember that in lab3 we registered several primitives with the binder, such as print_int to print an integer or ord to get the ASCII code of a character. You might want to look them up in your lab3/dragon-tiger/src/ast/binder.cc. We registered them so that they could be used without being defined explicitly in Tiger code, hence their “primitive” designation.

However, primitives must now be implemented in order to be found at link time. They cannot be implemented in Tiger, as we must interact with the underlying operating system.

In this lab, we will implement all those primitives in C++ in order to build a libruntime.a which will then be linked with the object code produced from the output of our Tiger compiler. Since we are using a POSIX system (interfaces with the operating system are well-defined), we will create a runtime which works on any POSIX system in directory src/runtime/posix.

  1. In src/runtime/posix/runtime.hh, look at the various prototypes of the functions you will have to implement. Notice the extern C around the code so that the external (link) name of those functions is kept the same and not mangled according to the C++ rules.

Compiling and linking

A new script compile is available at the top-level of the compiler once you have configured it. You can give it a tiger program or “-” to denote standard input, and it will produce an executable a.out containing your Tiger code linked with the runtime library.

Try it with:

$ echo "print_int(42)" | ./compile -
% ./a.out
UNIMPLEMENTED __print_int

At this stage, the program does not work yet, since you haven’t implemented the print_int primitive in the runtime.

The compile program is a shell script performing those steps:

Don’t hesitate to read compile and look at the various arguments used if you want to understand what happens behind the scene.

Implement the runtime

  1. Implement the runtime in file src/runtime/posix/runtime.cc. You might want to do it in the order described in the header file to get the best out of the automated tests.

If you want, you might implement another runtime for other systems (such as an Arm-based microcontroller), but you will have to interact with the environment using, for example, the semihosting capabilities which let you exchange data with a monitor program.