Humboldt-Universität zu Berlin
ZE Rechenzentrum (CMS)
Abt. Systemsoftware und Kommunikation
Dr. rer. nat. Andreas Kunert
HU-CMS-Logo  HU-Berlin Logo

Jasmin-Crashkurs

Motivation

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.

Einleitendes Beispiel

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

Aufbau einer Jasmin-Datei

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:

Iint
Ffloat
Zboolean(Achtung! B steht für byte (wird aber im Praktikum nicht gebraucht))
Vvoid(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