Zwischensprache:
Der Code für die Kanonisierung des Zwischencodes, wie in Vorlesung 8
vorgestellt, ist hier verfügbar, zusammen
mit den Hilfspaketen List.java und Utils.java.
Zum Einbinden in den eigenen Code, sollte für jeden Methodenrumpf
this.body folgendes Codestück ausgeführt werden:
LinkedList blks = IntmTrafo.intmTrafo(this.body);
this.canon_body = blks;
Interpreter:
Um das Testen des MiniJava Compilers zu vereinfachen, ist
hier ein Interpreter
für die Zwischensprache verfügbar.
Als Eingabe nimmt der Interpreter eine Folge von Funktionsdefinitionen,
die den Rümpfen der Methoden in MiniJava entsprechen.
Im Kopf der Funktionsdefinition muss der Name (mit beginnendem
"L"!) und die Parameterliste angegeben sein. In dieser Liste
kennzeichnet das Schlüsselwort REG ein (abstraktes) Register
und LOC eine Location auf dem Frame. Das erste Argument der
Parameterliste, muss ein Register
mit dem THIS Objekt sein. Als Beispiel, hier die Struktur der Factorial
Funktion im Zwischencode:
FCT LFactorial$main(REG t1, REG t77) =
{
...
}
Links zu weiteren Beispielen finden sich am Ende der Web page.
Zum Ausdrucken des Codes kann der pretty-printing Visitor in PPIntm verwendet werden (neue Version im package IntmTrafo.
Diese Funktion muss für jeden Methodenrumpf in der Symboltabelle
aufgerufen werden. Der Kopf der Funktionsdefinition muss separat aus
der weiteren Information in der Symboltabelle erzeugt werden.
Wichtige Konventionen:
- Registernamen müssen mit t beginnen, zB: t33
- Funktionsnamen und labels müssen mit L beginnen, zB: Lfact
- Das Resultatregister muss t2 sein (oder mit -r name
setzen), der Framepointer muss
t32 sein (oder mit -f name setzen).
- Der this-pointer muss t1 heissen und der erste Parameter der
Funktion sein (oder mit -t name setzen).
- Als "calling convention" werden vor dem Aufruf einer Funktion werden sämtliche Register abgespeichert.
D.h. es ist (noch) nicht nötig die Funktion procEntryExit1
aus der Vorlesung zu implementieren. In der Endversion der
Zwischensprachengenerierung müssen sämtliche callee-save
Register durch den erzeugten Code gesichert werden.
Dann ist eine einfachere "calling convention" im Interpreter zu
verwenden.
- Kein Sprung in einen Basisblock (dies kann durch Fusion von basic blocks bei der Erzeugung von Traces passieren). D.h. im Zwischencode muss vor jeder Label Definition ein JUMP oder CJUMP stehen (als Ende des vorherigen Basisblocks).
Ab Version 1.3 nicht mehr nötig.
Weitere nützliche Informationen:
- Laufzeitwerte werden im Interpreter als A int für
Adressen und I int für integers repräsentiert.
- Der Stack startet bei Adresse 8000 und wächst nach unten (zu
kleineren Adressen), der Heap startet bei Adresse 9000 und wächst
nach oben. Mit flag -C werden die aktuellen Werte für jeden
Methodenaufruf ausgedruckt.
Der Interpreter (Name: muHwI) wird mit dem Filenamen des Zwischencodes und
den Parametern der L...$main Funktion aufgerufen:
./muHwI F3.intm 4
Während der Ausführung druckt der Interpreter Meldungen über die Ausführung bestimmter Kommandos aus (z.B. Funktionsaufrufe mit Parameterwerten). Am Ende wird das Resultat ausgegeben.
Name für spezielle externe Funktionen, die der Interpreter kennt:
- L_halloc_obj: Allokierung eines heap objects; Parameter: Grösse
- L_init_obj: Initialisierung eines heap objects; Parameter: Pointer zum Objekt, Grösse
- L_halloc_arr: Allokierung eines Arrays; Parameter: Grösse
- L_init_arr: Initialisierung eines Arrays; Parameter: Pointer zum Objekt, Grösse (Indizierung startet mit 0; Grösse ist im Index -1 abgelegt)
- L_print: Ausdrucken eines Wertes; Parameter: Wert (Integer oder Adresse)
Hier die wichtigsten Optionen:
Usage: muHwI [options] <file>
-? --help show usage information
-v --verbose be verbose
-T Traces --defTrace=Traces default tracing flags
-C --callTrace trace function calls
-J --jumpTrace trace jumps
-M --moveTrace trace moves
-A --allocTrace trace memory allocations
-P --ParamTrace trace parameter passing
-R --regMap print register maps
-H --memMap print memory maps
-L --labMap print label maps
-D --debugging debug interpreter itself
-f t32 --framepointer=t32 name of frame pointer register
-s t98 --stackpointer=t98 name of stack pointer register
-h t99 --heappointer=t99 name of heap pointer register
-t t1 --thispointer=t1 name of this pointer register
-o FILE --output=FILE output file
Weitere Beispiel für Zwischencode (so wie er von muHwI eingelesen wird):