Amiga Assembler – Teil 3
Im letzten Amiga Assembler Teil habe ich euch das Status Register gezeigt und wir haben Subroutinen geschrieben. Für die meisten Programme braucht man aber mehr: Konditionen und Schleifen. Genau diesen Themen widme ich mich im dritten Teil.
Amiga Assembler – Teil 3
Die Schwierigkeit Assembler zu lernen liegt meiner Meinung nach daran, dass der Aufbau eines Programms weniger einfach durch bloßes hinsehen zu erfassen ist. Jede höhere Sprache strukturiert den Code schon durch die Regeln die gelten. Ein if ist fast bei allen Sprachen gleich, ebenso die Strukturen von Schleifen. Kennt man eine Sprache, dann kann man durch bloßes ansehen von Source Code die andere Sprache „lernen“ oder zumindest wesentliche Regeln ableiten. Mit Assembler hatte ich es bisher schwer, nach den ersten Übungen beginne ich aber die ersten Befehle zu verstehen. Einmal Verstanden wird der Assembler Code plötzlich auch auf den ersten, oder zweiten Blick erfassbar.
Abfragen
In höheren Programmiersprachen gibt es unterschiedliche konditionelle Kommandos. Am bekanntesten sind if/else Statements, es gibt aber auch oft switch und der eher weniger eingesetzte dreiteilige Befehl (in C mit ABFRAGE?OPTION1:OPTION2 dargestellt). Auf Assembler Ebene wird des ein wenig anders gemacht. Ein Teststatement prüft einen Wert aus einem Datenregister. Dementsprechend werden Flags im Status Register gesetzt. Abhängig der gesetzten Flags kann man auf unterschiedliche Adresse im Code springen. Da es mehrere Flags gibt ergeben sich mehrere Möglichkeiten.
Ein einfaches if/else Statement:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | START: ; einfaches if / else move.w #1,d0 tst.w d0 beq IF_PATH bne ELSE_PATH bra .exit nop .exit: rts IF_PATH: move.l #1,d1 rts ELSE_PATH: move.l #2,d1 rts |
Wie der einige neue Befehle. Diese sind kurz erklärt:
- TST
testet einen Operanden. Setzt das Zero Flag, sofern d0 den Wert 0 hat - BEQ
Branch if equal – war der vorherige Befehl ein erfolgreicher Vergleich, dann ist das Zero Flag gesetzt. Falls das zutrifft wird auf die angegebene Adresse gesprungen, andernfalls passiert nichts. - BNE
Branch if not equal – wie BEQ nur der andere Fall, also das Zero Flag darf nicht gesetzt sein
Das if/else funktioniert wie folgt. Zuerst wird ins Datenregister d0 eine 1 geschrieben. TST testet dieses und setzt das Zero Flag nicht (da 1 nicht 0 ist). Der BEQ Befehl schlägt fehl, da das Zero Flag nicht gesetzt ist, es wird nicht auf die Sprungmarke gesprungen. Die nächste Prüfung mit BNE trifft zu, das Zero Flag ist nicht gesetzt, weiter geht es im Else Zweig und der Wert 2 wird ins Datenregister d1 geschrieben (siehe Screenshot). Aufgabe: um den if Fall zu testen musst du nur im ersten move Befehl den #1er mit #0 ersetzten, dann wird 1 ins d1 Register geschrieben.
So ein binäres wenn/dann ist ja nett, manchmal braucht man aber einen dreiteiligen Vergleich und da würde mit 2 kombinierten if/else Abfragen viel Code erzeugt, der nicht nötig ist. Eine dreiteilige Abfrage lässt sich durch die Nutzung weiterer Flags im Status Register umsetzen. Beispielsweise zusätzlich mit dem Negative Flag:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | START: ; dreiteilige Abfrage clr d0 sub.w #1,d0 tst.w d0 beq IF_PATH1 bmi IF_PATH2 ; ELSE PATH bra .exit nop .exit: rts IF_PATH1: move.l #1,d1 rts IF_PATH2: move.l #2,d1 rts |
Der neue Befehl:
- BMI
Branch on Minus – wenn der vorherige Befehl das Negative Flag gesetzt hat, dann wird an die angegebene Adresse gesprungen
Anders als beim if/else zuvor können wir nicht nur 2 Pfade erstellen sondern sogar 3. Entweder es passiert Pfad 1 vom BEQ, der Pfad 2 vom BMI oder es geht im Hauptprogramm normal weiter.
Schleifen
Es gibt zwei Arten von Schleifen:
- Kopfgesteuerte Schleifen
das sind alle Schleifen die vor dem Schleifenkörper die Prüfung machen ob dieser ausgeführt wird. Beispiele sind die for oder while Schleifen. - Fußgesteuerte Schleifen
das sind alle Schleifen die den Körper ausführen und erst danach die Prüfung durchführen. Bekanntes Beispiel aus den meisten Sprachen die do-while Schleife.
Genau diese Strukturen können wir auch in Assembler nachbauen. Das sieht wie folgt aus:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | START: ; kopfgesteuerte Schleife zählt hoch clr d0 .loop: cmp.w #10,d0 beq .done add.w #1,d0 ; do something ; ; bra .loop .done: ; kopfgesteuerte Schleife zählt runter move.w #10,d0 .loop: cmp.w #0,d0 beq .done sub.w #1,d0 ; do something ; ; bra .loop .done: |
Die Kommentare im Code sollten eigentlich selbsterklärend sein. Es gibt aber einige neue Befehle:
- CLR
löscht den Wert aus dem Register und setzt es auf 0. Alternativ hätten wir auch ein move #0,d0 machen können - CMP
Vergleicht die beiden angegebenen Werte und setzt bei erfolg das Zero Flag. - BRA
Branch always – es wird immer auf die angegebene Adresse gesprungen
Ich habe im Screenshot von MonAm die beiden Schleifen markiert. Wie man erkennt sind die Sprungmarken zum Unterschied der Subroutinen nicht mehr benannt, es stehen nur noch die Adressen dort. Das liegt daran, dass wir lokale Sprungmarken (mit einem . am Anfang) verwendet habe. Im Unterschied zu globalen Sprungmarken müssen diese nicht global eindeutig sein. Das ist bei Schleifen sinnvoll, da wir sonst alle Schleifen durchnummerieren müssten.
Wer genau aufgepasst hat bemerkt, dass der VASM Compiler eine Optimierung vorgenommen hat. Aus dem cmp #0,d0 wurde ein tst d0. Der Befehl mit nur einem Argument ist offensichtlich effizienter als jener mit 2 Argumenten. Der Compiler hat also direkt Einfluss auf unseren Code und man darf sich nicht wundern, wenn man sich nicht sofort im eigenen Code zurecht findet.
1 2 3 4 5 6 7 8 9 10 11 | START: ; fußgesteuerte Schleife move.w #10,d0 .loop: ; do something ; ; dbf d0,.loop .done: nop rts |
Wie man zweifelsohne erkennt wird die Funktionalität der Schleife durch ein einzigen neuen Befehl gesteuert:
- DBF
Test condition, decrement and branch heißt dieser Befehl und macht genau das. Solange das angegebene Register nicht 0 ist wird dessen Wert reduziert und die angegebene Sprungmarke ausgeführt.
In dem Beispiel setzen wir das Register mit 10 und lassen den Code der Schleife 11 mal ausführen. Erst dann ist der Wert im Register 0, wird dann noch einmal auf einen negativen Wert gesetzt und die Schleife endet wie im Screenshot zu sehen.
Fazit
Wir haben einige neue Befehle gelernt und damit Abfragen gebaut. In Assembler kann man if/else Abfragen genau so einfach wie Schleifen bauen. Je nach Anforderung hat man unterschiedliche Möglichkeiten. Ich habe euch einige davon gezeigt.
Wie geht es dir mit Assembler Code? Bist du meinem Tutorial bis hier gefolgt und wird die Sprachsyntax langsam verständlich so wie für mich?
Alle Artikel dieser Artikelserie:
Programmieren auf dem Amiga
Programmieren auf dem Amiga – Teil 2
Amiga Assembler
Amiga Assembler – Teil 2
Amiga Assembler – Teil 3
Amiga Assembler – Teil 4
Amiga Assembler – Teil 5
Amiga Bibliotheken verwenden