Humboldt-Universität zu Berlin ZE Rechenzentrum (CMS) Abt. Systemsoftware und Kommunikation Dr. rer. nat. Andreas Kunert |
Die Zielsprache, die der von Ihnen zu implementierende Compiler generieren soll, heißt Jasmin. Es handelt sich dabei um einen Assemblercode, der mittels des Jasmin-Assemblers direkt in Bytecode für die Java Virtual Machine (JVM) übersetzt werden kann.
Für die Wahl von Jasmin als Zielsprache gab es mehrere Gründe, die wir Ihnen nicht vorenthalten wollen. Durch den Jasmin-Assembler sind Sie in der Lage, den von Ihrem Compiler generierten Code wirklich ausführen zu lassen. Sowohl das Verständnis des Compilers als vor allem auch die Fehlersuche werden dadurch stark vereinfacht. Im Gegensatz zum Assemblercode einer realen Maschine (Intel, Sparc, ...) hat Jasmin den Vorteil, leichter verständlich zu sein (zumindest auf der Ebene, in der wir es für das Praktikum benötigen). Parallel lernt man dabei auch Java sehr viel besser kennen (z.B. warum short gegenüber int keinen Speichervorteil bringt u.v.a.).
Den Jasmin-Assembler kann man auf der Jasmin-Homepage herunterladen. Zusätzlich sollte man sich den Java-Disassembler D-Java von der D-Java-Homepage besorgen. Mittels D-Java kann man aus class-Dateien Jasmincode generieren. Es hat sich gezeigt, daß die schnellste Art, Jasmin zu lernen, darin besteht, (sehr) kleine Java-Progrämmchen zu schreiben, diese mit javac zu kompilieren und anschließend mit D-Java zu disassemblieren. Eine Vorgehensweise, die wir nur wärmstens empfehlen können.
Da sowohl Java als auch die JVM (und damit auch Jasmin) objektorientiert arbeiten, müssen wir unser strukturiertes C0-Programm irgendwie objektorientiert "einpacken". Das ist zwar nicht sonderlich schwer, aber wichtig zu wissen. Wir generieren eine Klasse, die ausschließlich statische Methoden besitzt, wobei jeder Funktion des C0-Programms genau eine statische Methode zugeordnet wird (void main()
wird dabei zu public static void main(String argv[])
). Da wir nie Objekte instantiieren wollen, müssen wir uns nicht mit den Jasminkonstrukten für Objekterzeugung, Vererbung, usw auseinandersetzen.
Die Java Virtual Machine ist ein sogenannter Stackprozessor. Welche Konsequenzen damit verbunden sind, sollte in der Vorlesung behandelt worden sein. In diesem Text wird davon ausgegangen, daß der Leser zumindest eine vage Vorstellung von Stackmaschinen hat.
Im folgenden sind ein kleines Java-Programm und dessen Pendant in Jasmincode zu sehen. Der Jasmincode wurde dabei durch D-Java generiert. Keine Panik! Im anschließenden Text wird der Jasmincode schrittweise auseinandergenommen.
1 public class test { 2 3 static int i = 0; 4 static boolean b; 5 6 public static void main(String argv[]) { 7 System.out.println(add(1,i)); 8 } 9 10 public static int add(int a, int b) { 11 int c; 12 c = a + b; 13 return c; 14 } 15 } 16
1 ; 2 ; Output created by D-Java (mailto:umsilve1@cc.umanitoba.ca) 3 ; 4 5 ;Classfile version: 6 ; Major: 46 7 ; Minor: 0 8 9 .source test.java 10 .class public synchronized test 11 .super java/lang/Object 12 13 14 .field static i I 15 .field static b Z 16 17 ; >> METHOD 1 << 18 .method public <init>()V 19 .limit stack 1 20 .limit locals 1 21 .line 1 22 aload_0 23 invokenonvirtual java/lang/Object/<init>()V 24 return 25 .end method 26 27 ; >> METHOD 2 << 28 .method public static main([Ljava/lang/String;)V 29 .limit stack 3 30 .limit locals 1 31 .line 7 32 getstatic java/lang/System/out Ljava/io/PrintStream; 33 iconst_1 34 getstatic test/i I 35 invokestatic test/add(II)I 36 invokevirtual java/io/PrintStream/println(I)V 37 .line 8 38 return 39 .end method 40 41 ; >> METHOD 3 << 42 .method public static add(II)I 43 .limit stack 2 44 .limit locals 3 45 .line 12 46 iload_0 47 iload_1 48 iadd 49 istore_2 50 .line 13 51 iload_2 52 ireturn 53 .end method 54 55 ; >> METHOD 4 << 56 .method static <clinit>()V 57 .limit stack 1 58 .limit locals 0 59 .line 3 60 iconst_0 61 putstatic test/i I 62 return 63 .end method 64
Fangen wir mit der Analyse des Jasmincodes einfach ganz oben an. Alle Zeilen, die mit einem Semikolon beginnen, sind Kommentare und werden von der JVM erfolgreich ignoriert, d.h. die Zeilen 1 bis 7 könnte man ohne weitere Konsequenzen entfernen.
1 ; 2 ; Output created by D-Java (mailto:umsilve1@cc.umanitoba.ca) 3 ; 4 5 ;Classfile version: 6 ; Major: 46 7 ; Minor: 0
Im fertig implementierten Compiler besteht zwar prinzipiell keine Notwendigkeit, Kommentare im Zielcode einzufügen. In der Entwicklungs- und Debuggingphase sind sie jedoch recht hilfreich und auch ansonsten spricht nichts dagegen, den Zielcode optisch etwas aufzuwerten.
Nach diesen ersten Kommentarzeilen beginnt der eigentliche Code mit drei Direktiven: source
, class
und super
:
9 .source test.java 10 .class public synchronized test 11 .super java/lang/Object
.source
gibt an, welchen Namen die ursprüngliche Quelldatei hatte. Diese Angabe dient dazu, bei Fehlermeldungen oder beim Debuggen aus dieser Datei die Quellcodezeilen laden zu können. Die Angabe von .source
ist optional und kann beim Praktikumscompiler weggelassen werden.
Mittels .class
gibt man den Namen und die Attribute der Klasse an. In den vom Praktikumscompiler generierten Programmen sollten hier immer (wie im Beispiel) zuerst public synchronized
und dann ein selbstgewählter Klassenname stehen.
.super
gibt an, von welcher Klasse die aktuelle direkt abgeleitet ist. Hier bietet sich für das Praktikum die einfachste aller Java-Klassen, java/lang/Object
, an (bitte bei der Schreibweise aufpassen: /
statt .
).
In den anschließenden Zeilen erfolgt die Deklaration der statischen Variablen.
14 .field static i I 15 .field static b Z
Statische Variablen werden mit dem Schlüsselwort .field
gefolgt von static
eingeleitet. Es folgt der Variablenname und der Typ wobei in Jasmin folgende Kürzel verwendet werden:
I | int | ||
F | float | ||
Z | boolean | (Achtung! B steht für byte (wird aber im Praktikum nicht gebraucht)) | |
V | void | (nur für leere Rückgabewerte von Methoden) |
Der Rest der Datei besteht "nur" noch aus Methoden, wobei zwei eine besondere Bedeutung haben: init
und clinit
. Doch zunächst sehen wir uns die allgemeine Syntax von Methoden an und zwar am Beispiel der Feld-Wald-und-Wiesen-Methode add
.
42 .method public static add(II)I 43 .limit stack 2 44 .limit locals 3 45 .line 12 46 iload_0 47 iload_1 48 iadd 49 istore_2 50 .line 13 51 iload_2 52 ireturn 53 .end method
Der Methodenkopf ist folgendermaßen aufgebaut: zuerst das Schlüsselwort .method
, gefolgt von Attributen und dem Methodenname. Dahinter stehen in Klammern die Typen der Parameter (hier zwei integer) und abschließend der Rückgabetyp (ebenfalls ein integer).
In den folgenden zwei Zeilen werden zwei wichtige Grenzen gesetzt: die maximale Größe des Stacks (.limit stack
) und die Anzahl lokaler Variablen (.limit locals
). Bei letzterer Angabe ist zu beachten, daß die Parameter der Funktion als Variable mitgezählt werden.
Dann folgt bis zum abschließenden .end
der eigentliche Code der Methode. In diesem Fall sieht man, daß der Wert der nullten und ersten lokalen Variable (also die Parameter) auf den Stack gelegt werden und anschließend in die zweite lokale Variable (im Java-Programm "c") gespeichert werden. Dann wird der Inhalt der zweiten lokalen Variable wieder auf den Stack gelegt und durch ireturn
als Rückgabewert benutzt.
Die .line
Anweisungen, die zwischendurch eingestreut sind, könnte man als "nützliche Kommentare" bezeichnen. Sie werden bei der Ausführung ignoriert (können dementsprechend auch weggelassen werden). Im Fehlerfall oder beim Debuggen dienen sie jedoch dazu, dem Nutzer mitzuteilen, in welcher Zeile der Quelldatei er sich eigentlich befindet.
Bleiben noch die beiden speziellen Methoden übrig. Die Methode init
stellt den Konstruktor dar. Da unsere Klasse keinen eigenen definiert hat, besteht dieser nur aus dem Minimalcode, der notwendig ist, um den Superkonstruktor aufzurufen.
18 .method public <init>()V 19 .limit stack 1 20 .limit locals 1 21 .line 1 22 aload_0 23 invokenonvirtual java/lang/Object/<init>()V 24 return 25 .end method
Da wir im Praktikum ebenfalls nur eine Klasse erstellen, die nie instantiiert werden soll, kann die obig stehende init
-Funktion unverändert übernommen werden.
Die Methode clinit
ist der sogenannte Klasseninitialisierer. Dieser dient dazu, statische Variablen zu initialisieren. Im Beispiel kann man sehen, wie die statische Variable i
den Wert 0
zugewiesen bekommt.
56 .method static <clinit>()V 57 .limit stack 1 58 .limit locals 0 59 .line 3 60 iconst_0 61 putstatic test/i I 62 return 63 .end method
Page created: Monday, 06-Oct-2003 14:00:00 MET DST Last update: Friday, 14-February-2014 17:53:48 CET Datenschutzerklärung |