Introduction
The goal of this third graded assignment is to modify your minischeme compiler so that it optimises tail calls, using the technique presented in the course.
Identifying tail calls
To optimise tail calls, they must first be identified. This is precisely the goal of the T function presented in the course.
Translating this function to Scala code is however not as easy as it seems, because it identifies calls with a unique number attached to them. To simulate this, a natural idea is to store the trees corresponding to tail calls in a set. Unfortunately, trees being Scala case classes, they are compared by content, not by identity! This is a problem here, as it can lead to non-tail calls being incorrectly identified as tail calls, as in the following function:
(define print-int-twice (lambda (i) (print-int i) (print-int i)))
If sets of trees are used to identify tail calls, the first
call to print-int
will incorrectly be considered
as a tail call! That's because print-int-twice
contains another call to the same function that, from the
point of view of Scala's notion of equality for case classes,
is equal to the first.
To solve this problem, trees should be wrapped into an object that gurantees that they are compared by identity, before being stored into a set. The wrapper class can be defined as follows:
class Eq[T <: AnyRef](private val obj: T) { override def equals(that: Any): Boolean = that.isInstanceOf[Eq[T]] && (this.obj eq that.asInstanceOf[Eq[T]].obj) }
Optimising tail calls
Once tail calls have been identified, optimising them is very easy, at least in the context of this project. The course provides all the necessary information.
Epilogue removal
The epilogue code of functions that always end with a tail call should be removed. These functions can be identified using the predicate E presented in the course.
Solution submission
Your solution must be submitted as a ZIP archive containing exactly the following files - not one more, not one less:
README (short description of your solution) compiler/minischeme/Code.scala compiler/minischeme/EtaExpander.scala compiler/minischeme/FileReader.scala compiler/minischeme/Generator.scala (modified to eliminate tail calls) compiler/minischeme/Instruction.scala compiler/minischeme/Interpreter.scala compiler/minischeme/Label.scala compiler/minischeme/Main.scala compiler/minischeme/NameAnalyzer.scala compiler/minischeme/Opcode.scala compiler/minischeme/Parser.scala compiler/minischeme/Primitives.scala compiler/minischeme/Register.scala compiler/minischeme/Report.scala compiler/minischeme/Representable.scala compiler/minischeme/Scanner.scala compiler/minischeme/SeqReader.scala compiler/minischeme/Symbol.scala compiler/minischeme/Token.scala compiler/minischeme/Tree.scala compiler/minischeme/TreePrinter.scala
Notice in particular that you should disable closure conversion for this submission! This is to make sure that bugs in your closure converter do not influence this submission.
The README
file should contain a short
description of your solution and its peculiarities, as well as
the name of all its authors. With the exception of
Generator.scala
, and possibly
Main.scala
, the files you submit should be
identical to the ones you received. If they are not, explain
why in the README
file.
To help you check the contents of the archive, we provide you with a Scala script called SubCheck5.scala. When run with your archive as argument, this script should not report any problem.
Remember that according to our submission regulations, we do not accept solutions that fail to compile.
The archive containing your solution should be submitted on Moodle before 23:55, April 23, 2009.