|
1 | | -## Supported types |
2 | | - |
3 | | -Unterstützte Typen sind in [Types](../lib/src/types.rs) definiert. |
4 | | -Mögliche Kombinationen sind den [Tests](../lib/testcases) zu entnehmen. |
5 | | - |
6 | | - |
7 | 1 | ## Parser |
8 | 2 |
|
9 | | -Geschrieben von: Viktoria Gönnheimer, Sander Stella |
| 3 | +Geschrieben von: Tori Gönnheimer, Sander Stella |
10 | 4 |
|
11 | | -Der Parser akzeptiert den text eines Java Programms und gibt einen Abstract Syntax Tree (AST) zurück. |
| 5 | +Ausführliche Mithilfe (v.a. beim Schreiben der Grammatik): Val Richter |
| 6 | + |
| 7 | +Der Parser akzeptiert den Text eines Java Programs und gibt einen Abstract Syntax Tree (AST) zurück. |
12 | 8 | Dafür wird die Libray [pest.rs](https://pest.rs/) verwendet, um das Inital Parsing durchzufüren. |
13 | | -Für dieses inital parsing nutzt pest unsere vorher definiete Gramatik. Bei der Gramatik wurde sich primär and der Vorlesugn orientiert mit signifikaten Abänderungen um das parsing zu vereinfachen, sowie den Spezifikationen der Library nachzukommen. |
| 9 | +Für dieses inital Parsing nutzt pest unsere vorher definiete Gramatik. |
| 10 | + |
| 11 | +Bei der Grammatik wurde sich Anfangs an den Hilfsmitteln der Vorlesung orientiert. Später wurde diese allerdings neu geschrieben, um sich stärker am verwendeten AST zu orientieren. Dadurch wurde auch die Implementation des restlichen Parsers sehr viel erleichtert. |
14 | 12 | Das Egebnis, welches Pest zurückgibt wird von uneren Parser-Funktionen analysiert und umgewandelt. |
15 | 13 | Dabei wird wie folgt vorgegangenen: |
16 | | -- Eine Funktion schaut sich die aktuelle Regel an |
17 | | -- Es wird der Entsprechende Code zu dieser Regel ausgeführt |
18 | | -- Sofern Subregeln in dieser Regel vorkommen, wird die entsprechende Funktion aufgerufen |
19 | 14 |
|
| 15 | +- Eine Funktion schaut sich die aktuelle Regel an |
| 16 | +- Es wird der Entsprechende Code zu dieser Regel ausgeführt |
| 17 | +- Sofern Subregeln in dieser Regel vorkommen, wird die entsprechende Funktion aufgerufen |
| 18 | + |
| 19 | +- Eine Funktion schaut sich die aktuelle Regel an |
| 20 | +- Es wird der entsprechende Code zu dieser Regel ausgeführt |
| 21 | +- Sofern Subregeln in dieser Regel vorkommen wird die entsprechende Funktion aufgerufen |
20 | 22 |
|
21 | 23 | ## Typechecker |
22 | 24 |
|
@@ -57,47 +59,72 @@ Folgende Fehler werden vom Typechecker erkannt: |
57 | 59 |
|
58 | 60 | ## Codegenerierung |
59 | 61 |
|
60 | | -ByteCode-Umwandlung, Bugfixes, StackSize und viele Verbesserungen: Val Richter |
| 62 | +Geschrieben von: Marion Hinkel und Benedikt Brandmaier im Pair Programming |
| 63 | + |
| 64 | +StackMapTable-Implementation, Mithilfe bei Transformation von DIR zu Bytes und sonstigem Bugfixing: Val Richter |
61 | 65 |
|
62 | | -Definition DIR(Duck Intermediate Representation), ConstantPool, LocalVarPool, Methoden zur Instruction-generierung, BugFixes, etwas ByteCode-Umwandlung und Umwandlung relativer in absolute Jumps: Marion Hinkel und Benedikt Brandmaier im Pair Programming |
| 66 | +Zur Bytecode-Generierung wird der Typed Abstract Syntax Tree (TAST) in Java Bytecode umgewandelt. |
| 67 | +Es wurden keine Libraries (wie z.B. [ASM](https://asm.ow2.io/javadoc/)) verwendet. |
| 68 | +Für die Codegenerierung wird eine Intermediate Representation (IR) genutzt, die eine Class-ähnliche Struktur |
| 69 | +(mit Konstantenpool, LocalVarpool, Methoden mit Code als Instruktionen, etc.) besitzt. |
| 70 | +Diese IR wird dann komplett manuell in Java Bytecode übersetzt. Dies hat sehr viel Zeit gekostet, |
| 71 | +da z.B. die Stack-Size, der Konstantenpool, LocalVarPool und alle Jumps manuell berechnet werden mussten. |
63 | 72 |
|
64 | | -Zur Bytecode-generierung wird der Typed Abstract Syntax Tree(TAST) in Java Bytecode komplett selber |
65 | | -umgewandelt. Dafür wird eine Intermediate Representation (IR) genutzt, die eine Class-ähnliche Struktur(mit Konstantenpool, LocalVarpool, Methoden mit Code als Instruktionen, etc.) |
66 | | -besitzt. Diese IR wird dann komplett manuell in Java Bytecode übersetzt. Dies hat dem Code-gen Team sehr viel |
67 | | -Zeit gekostet, da z.B. die Stack-Size, der Konstantenpool, LocalVarpool und die Jumps manuell berechnet werden mussten. |
| 73 | +Zudem hatten wir zeitweise eigene Instructions für relative Jumps implementiert, die wir dann in absolute Jumps umgerechnet haben, |
| 74 | +da wir dachten, dass die JVM keine relativen Jumps unterstützt. Dieser Glauben kam daher, dass die Ausgabe von `javap` die |
| 75 | +Ziel-Adressen von Jumps immer als absolute Adressen angezeigt hat. Es stellte sich dann aber heraus, dass die JVM eigentlich |
| 76 | +nur relative Jumps versteht und nur javap diese schon umgerechnet dargestellt hat. Aber selbst danch waren die relativen Jumps |
| 77 | +noch sehr fehleranfällig und hatten häufig off-by-one Errors. |
68 | 78 |
|
69 | | -Zudem hatten wir zeitweise eigene Relative Instructions implementiert, da wir dachten, dass die JVM keine relativen Jumps |
70 | | -unterstützt, hatten dann allerdings mit Try-and-Error herausgefunden, dass javap sich die absoluten Addressen ausrechnet |
71 | | -und für die JVM normale jumps als relative Jumps behandelt. Auch die relativen Jumps waren aber sehr fehleranfällig und |
72 | | -hatten häufig off-by-one Errors. |
| 79 | +Zudem musste eine [StackMapTable](https://docs.oracle.com/javase/specs/jvms/se20/html/jvms-4.html#jvms-4.7.4) per Hand implementiert werden, |
| 80 | +da die JVM unsere Klassen sonst nicht geladen hat. Die Implementation dieser war ebenfalls sehr zeitaufwendig, da an sich ein ganzer |
| 81 | +Typchecker für den generierten Bytecode implementiert werden musste, um korrekte StackMapTables zu generieren. |
73 | 82 |
|
74 | | -Zudem musste eine StackMapTable implementiert werden, da die JVM sonst unsere Klassen nicht lädt. |
75 | | -Das Troubleshooten von Testfehlern war auch sehr aufwending da oft javap gar nicht erst den Fehler im Klassencode ausgab |
| 83 | +Das Troubleshooten von Testfehlern war auch sehr aufwendig da oft javap gar nicht erst den Fehler im Klassencode ausgab |
76 | 84 | und wir mit einem Hex-Editor die Klassen von Hand analysieren mussten, da es auch kein anderes Tool gab, um solche Fehler |
77 | | -auszugeben und die Zeit fehlte ein Eigenes zu schreiben. |
| 85 | +auszugeben und die Zeit fehlte ein eigenes Tool dafür zu schreiben. |
78 | 86 |
|
79 | | -Da es auch keine Dokumentation gibt, die in etwa zeigt, welcher Bytecode für welche Operationen genutzt wird, mussten wir |
| 87 | +Da es auch keine gute Dokumentation gibt, die in etwa zeigt, welcher Bytecode für welche Operationen genutzt wird, mussten wir |
80 | 88 | uns die Bytecode-Spezifikationen anschauen und sehr viel mit Tools wie javap und [godbolt](https://godbolt.org/) arbeiten |
81 | | -in die wir manuell Java Code eingeben und schauten, was für Bytecode bei verschiedenen Operationenkombinationen generiert |
82 | | -wird, was sehr zeitaufwendig war. |
83 | | - |
84 | | -Auch sehr schwierig war die Implementation einer StackMapTable, da Java diese erwartet. Diese ist eine Tabelle, die für |
85 | | -jede Instruktion die Typen der Elemente auf dem Stack in komprimiertem Format angibt. Diese Tabelle muss manuell |
86 | | -erstellt werden und über die Typen aller Variablen, die in den Stack geschrieben wurden Bescheid wissen. |
| 89 | +(wir haben auch https://docs.oracle.com/javase/specs/jvms/se20/html/jvms-3.html genutzt, aber diese Dokumentation nutzt |
| 90 | +sehr spezifische Instruktionen und wurde deswegen selten genutzt) in die wir manuell Java Code eingeben konnten, um zu sehen, |
| 91 | +welcher Bytecode bei verschiedenen Operationskombinationen generiert wird, was sehr zeitaufwendig war. |
87 | 92 |
|
88 | 93 | ## Testing |
89 | 94 |
|
90 | | -Das Testen des Codegens war sehr aufwendig, er besteht aus diesen Schritten: |
91 | | - |
92 | | -1. Ein handgeschriebener TAST wird geladen |
93 | | -2. Eine Java Klasse wird erstellt die jede Methode im TAST aufruft |
94 | | -3. Die java Klasse wird mit javac kompiliert und ausgeführt, wobei die Ausgabe in einer Variable gespeichert wird |
95 | | -4. Fürs Debugging wird die Java Klasse mit javap in Bytecode umgewandelt und in eine Datei geschrieben |
96 | | -5. Der TAST wird in eine DIR umgewandelt und zu Bytecode umgewandelt |
97 | | -6. Der Bytecode wird in eine .class-Datei geschrieben |
98 | | -7. Die .class-Datei wird mit javap in Bytecode umgewandelt und in eine Datei geschrieben |
99 | | -8. Die vom Codegen generierte .class-Datei wird ausgeführt und die Ausgabe in einer Variable gespeichert |
100 | | -9. Die Ausgaben der richtigen Java Klasse und der vom Codegen generierten Klasse werden verglichen |
| 95 | +Geschrieben von: Val Richter |
| 96 | + |
| 97 | +Jeder Test besteht aus einer Java-Klasse (liegt jeweils in `lib/testcases`) und aus dem dazugehörigen getypten AST, welcher für jeden Test per Hand geschrieben wurde. Mit diesen beiden Teilen testen wir dann den Parser, Typchecker und die Codegenerierung. Außerdem testen wir auch, dass der handgeschriebene TAST korrekt ist. Wie diese Tests funktionieren, wird im Folgenden ausgeführt. |
| 98 | + |
| 99 | +Um zu testen, dass die handgeschriebenen TASTs korrekt sind, wird aus dem TAST ein syntaktisch korrektes Java-Programm geschrieben. Alle Typinformationen des TAST werden dabei ignoriert. |
| 100 | +Das so erstellte Java-Programm wird dann in eine Datei geschrieben und mit `javac` kompiliert. Daraufhin wird überprüft, dass die kompilierte `.class`-Datei identisch mit der `.class`-Datei ist, |
| 101 | +die man beim Kompilieren des originalen Java-Programms erhält. Eine nervige Besonderheit bei diesem Vorgehen ist, dass der vom AST generierte Java-Code die originale Java-Datei überschreiben muss, |
| 102 | +da der `javac` Compiler sonst minimal unterschiedliche `.class`-Dateien schreibt. Trotzdem war dieser Test sehr hilfreich, da beim handschriftlichen Schreiben der teilweise sehr großen TASTs, |
| 103 | +sehr oft kleine Fehler gemacht wurden, die ansonsten nur schwer zu finden wären. |
| 104 | + |
| 105 | +Außerdem schreiben wir bei dem Test des TASTs den erwarteten AST und TAST als `.json`-Dateien in den `lib/testcases` Ordner. Diese Dateien dienen vor allen bei der Implementation der anderen Teile des Compilers, da somit leicht sichtbar ist, wie der erwartete AST bzw. TAST aussehen sollten für den jeweiligen Test. |
| 106 | + |
| 107 | +Das Testen des Parsers war relativ leicht. Hier musste nur die jeweilige Java-Datei gelesen und in den Parser gegeben werden. Sobald dieser dann den erstellten AST zurück gibt, kann überprüft werden, ob er identisch mit dem handgeschriebenen TAST ist. Dabei werden alle Typinformationen vom TAST vorher entfernt (wodurch wir den AST erhalten), weil der Parser ja noch keine Typinformationen hinzufügt. |
| 108 | + |
| 109 | +Das Testen des Typcheckers läuft ähnlich ab. Der handgeschriebene TAST dient als erwartete Ausgabe des Typcheckers. Der AST, der als Eingabe für den Typchecker dient, wird über Entfernen der Typinformationen beim TAST generiert. |
| 110 | + |
| 111 | +Das Testen des Codegens war dagegen sehr viel aufwendiger. Die Eingabe für die Codegenerierung ist mit den handgeschriebenen TASTs bereits gegeben. Um die Ausgabe zu überprüfen, hätte man allerdings die erwarteten Bytes per Hand aufschreiben müssen. Das wäre zu aufwendig und fehleranfällig gewesen. Stattdessen testen wir, dass die von uns geschriebene `.class`-Datei sich identisch mit der von `javac` erstellten `.class`-Datei verhält. |
| 112 | + |
| 113 | +Dafür wurde zuerst die originale Java-Datei kompiliert. Zusätzlich wird eine Test-Datei geschrieben, welche eine `main` Funktion hat und alle Methoden der originalen Java-Datei aufruft. Über die handgeschriebenen TASTs wissen wir, welche Methoden die Klasse besitzt und welche Eingaben sie erwartet und was sie ausgibt. Die Eingaben werden pseudo-zufällig für den Test generiert. Falls die Methoden einen Wert ausgibt, schreibt die `main` Funktion der Test-Datei diesen Ausgabewert in die Konsole. Damit erhalten wir das erwartete Verhalten der originalen Java-Klasse. |
| 114 | + |
| 115 | +Im nächsten Schritt überschreiben wir nun die `.class`-Datei der originalen Java-Datei mit den Bytes, die wir aus der Codegenerierung erhalten. Dann können wir Test-Datei nochmal ausführen. Diesmal versucht java aber natürlich die von uns geschriebene `.class`-Datei zu lesen und zu benutzen. Wenn die Test-klasse dann die selben Ausgaben macht, wissen wir, dass sich unsere `.class`-Datei genauso wie die originale `.class`-Datei verhält und unsere Codegenerierung entsprechend richtig funktioniert. |
| 116 | + |
| 117 | +Zusätzlich nutzen wir in diesem Test auch das mit Java mitgelieferte Tool `javap`, welches uns erlaubt den originalen und den von uns generierten Bytecode zu disassemblieren. Wir schreiben die Ausgabe von `javap` dann in jeweils eine Datei (eine Datei für die originale von `javac` kompilierte Klasse und eine Datei für die von uns kompilierte Klasse). Diese Ausgaben sind zwar für den Test nicht notwendig, haben aber sehr geholfen bei Fehlersuche und Fehlerbehebung. |
| 118 | + |
| 119 | +## Supported types |
| 120 | + |
| 121 | +Unterstützte Typen sind in [Types](../lib/src/types.rs) definiert. |
| 122 | +Mögliche Kombinationen sind den [Tests](../lib/testcases) zu entnehmen. |
| 123 | + |
| 124 | +## Supported types |
| 125 | + |
| 126 | +Unterstützte Typen sind in [Types](../lib/src/types.rs) definiert. |
| 127 | +Mögliche Kombinationen sind den [Tests](../lib/testcases) zu entnehmen. |
101 | 128 |
|
102 | 129 | ## AST-Definition |
103 | 130 |
|
|
0 commit comments