Notes on Compilers

I set myself a goal to write a bootstrapping scheme compiler. I had a rough idea how it was done from Marc Feeley’s talk on writing a scheme->c compiler in 90 mins. I wrote up a parser quickly and made sure it was self-applicable. I didn’t work on the compiler project until a few weeks after that.

One day I remembered about it and got hacking! Wrote this scm but it didn’t quite make it. It was too inefficient and incomplete. When I tried to bootstrap it, it used up all possible memory quickly and crashed horribly. A really obvious reason for this was that I didn’t have garbage collection orr tail call optimization both of which are extremely important in scheme! In fact looking back: I did not have optimizations.

One interesting thing I learned thinking about how to improve that attempt was that garbage collection would be almost worthless if I did not also do tail call elimination. The stack frame is the root for GC to find live data from and wasted stack frames lying around would block the GC from getting rid of a lot of environment variables that are no longer used.

So I got my friends to collab on it with me and tried again moo. This time I implemented a garbage collector and a simple my stack based runtime with a trampoline in C. This was the first time I’ve ever implemented a GC so it was exciting to see it working in practice (A good way to test it is a recursive infinite loop). This compiler also has tail call optimization (since unlike the previous compiler it uses CPS tranformation, combining that with the trampoline gives you TCO for free).

Moo manages to successfully compile a bunch of interesting programs like binary trees, derivatives and the n-queens solver from SICP.. but it was having some huge code-blowups in the output it produces. So I asked for some help on ltu and thanks to Eric Bergstrome who noticed an explosion in the CPS code we got a big reduction! There were a few bugs in it and my friend did some incredible work pushing it further and further but it just would not take off.

So I tried again with a different approach - XP/test driven development, I wanted to make all the parts modular and independent and thoroughly tested. It was also based on building up the way instead of down: Moloch. Development slowed and I started thinking about the big picture more than the details: I felt like it was silly to build my own stack data structure in C, and using a trampoline means the C stack itself would “vibrate” a lot. There is a very clever way to make use of the C stack effectively called Cheney on the MTA (Instead of vibrating with lots of tiny little jumps it waits and waits and does a gigantic leap “Off the empire state building”) but I’m not using that advanced technique for this compiler! Also I was starting to feel a bit bad about spending so much time on it, I felt like I was neglecting the other parts of my life.

At this point I have learned that writing a scheme compiler is much harder than I first thought!

I studied the RABBIT and ORBIT papers more deeply * RABBIT: ftp://publications.ai.mit.edu/ai-publications/pdf/AITR-474.pdf * ORBIT: repository.readscheme.org/ftp/papers/orbit-thesis.pdf and I realized that I was missing a really important optimization: Closure analysis (where you decide whether or not you can stack allocate a closure). More generally I realized this - A proper compiler is more than just a translator from one language to another, it must also optimize the code. I felt like I had been missing half the picture this whole time.

Translation

A quote from Olin Shivers thesis: > CPS-based compilers raise the stakes even further. Since all control and environment structures are represented in CPS Scheme by lambda expressions and their application, after CPS conversion, lambdas become not merely common, but ubiquitous. The compiler lives and dies by its ability to handle lambdas well. In short, the compiler has made an explicit commitment to compile lambda cheaply — which further encourages the programmer to use them explicitly in his code. > Implementing such a powerful operator efficiently, however, is quite tricky. Thus, research in Scheme compilation has had, from the inception of the language, the theme of taming lambda: using compile-time analysis techniques to recognise and optimise the lambda expressions that do not need the fully-general closure implementation.

Optimization

Books/Papers/Compilers

What else is out there

I tried reading other peoples code a bit to try to learn some stuff. It was interesting but I don’t feel like it helped me much. Larger compiler projects are even harder to get into

Mini-compilers

Since the big compiler was too hard on my first two tries, I decided to work on much smaller easier compiler projects. This would give me useful practice and get me closer to my goal.