BASIC-Compiler

Einbinden von eigenem Assembler-Quellcode


Obwohl der BASIC-Compiler einen hohen Funktionsumfang bietet, lassen sich nicht alle Anforderungen bzw. Programmieraufgaben mit BASIC-Mitteln lösen. In solchen Fällen muss man die gewüschten Funktionen in Assembler schreiben und vom BASIC-Programm aus aufrufen. Eine Möglichkeit besteht darin, den Maschinencode der eigenen Assembler-Routine in DATA-Zeilen zu hinterlegen, im Programmablauf mit READ zu lesen und mit POKE bzw. DOKE diesen in den Arbeitsspeicher (RAM) zu schreiben (z.B. nach TOP) und dann die Routine mit CALL oder USR aufzurufen. Das Beispiel liest den aktuellen Wert des R-Register im Mikroprozessor aus und zeigt ihn als Dezimalzahl an:

DEFUSR0=TOP
DATA &HED,&H5F,&H6F,&H26,&H00,&HC9
A=TOP
FOR I=1 TO 6
  READ B
  POKE A,B
  A=A+1
NEXT I
PRINT "R-Register: ";USR0(0)

Dies ist ein klassischer Weg, der recht unkompliziert ist. Allerdings ist dieser Weg uneffektiv bzgl. des benötigten Speicherplatzes und eignet sich deshalb nur für kleinere Maschinencoderoutinen. Außerdem kann es auch schwierig werden, wenn der eigene Maschinencode nicht relokatibel und somit an feste Adressen gebunden ist.

1. ASM-Anweisung und ASM-Funktion

Der JKCEMU-BASIC-Compiler bietet die Möglichlkeit, mit der Anweisung ASM sowie der Funktion ASM eigenen Assembler-Quelltext direkt im BASIC-Programm einzubinden. Damit lassen sich viele Dinge deutlich eleganter lösen. Das Beispiel von oben sieht dann so aus:

PRINT "R-Register: ";ASM(" CALL lese_Reg_R")
END
ASM "lese_Reg_R:"
ASM " LD A,R"
ASM " LD L,A"
ASM " LD H,0"
ASM " RET"

Man kann auch auf die eigenständige Routine verzichten und das R-Register so auslesen:

ASM " LD A,R"
ASM " LD L,A"
ASM " LD H,0"
ASM " PUSH HL"
PRINT "R-Register: ";ASM(" POP HL")

Insbesondere das letzte Beispiel macht deutlich, dass das Einbinden von eigenen Assembler-Quellcode direkt in die Code-Erzeugung des BASIC-Compilers eingreift. Aus diesem Grund gilt: Die ASM-Anweisung und die ASM-Funktion sind nur für Experten gedacht, die genau wissen, was sie tun!

Zwischen den einzelnen BASIC-Anweisungen und -Funktionen bestehen keine Abhängigkeiten bzgl. der Register, d.h., Sie brauchen bei der Verwendung der ASM-Anweisung und der ASM-Funktion für den BASIC-Compiler keine Register retten.

2. Zugriff auf BASIC-Variablen

In eigenen Assembler-Routinen kann auch auf BASIC-Variablen zugegriffen werden. Die Adresse einer Variable erfährt man mit der BASIC-Funktion VARPTR. Integer- und Long-Variablen sind 2 bzw. 4 Bytes groß und speichern den numerischen Wert im Zweierkomplement mit dem niederwertigsten Byte zuerst (Little Endian).

Decimal-Variablen sind 6 Bytes groß und speichern den Wert als gepackte BCD-Zahl im Big-Endian-Format (höchstwertiges Byte zuerst). Das oberste Halbbyte des ersten Bytes enthält in den Bits 4 bis 6 die Anzahl der Nachkommastellen und im Bit 7 das Vorzeichen. Das untere Halbbyte des ersten Bytes sowie die 10 Halbbytes der nachfolgenden fünf Bytes enthalten die 11 Dezimalstellen.

String-Variablen sind 2 Bytes groß und enthalten einen Zeiger auf die eigentliche Zeichenkette, die mit einem Nullbyte abgeschlossen ist.

Achtung! Zeichenketten werden in einem dynamischen Speicher abgelegt, der durch das compilierte Programm verwaltet wird. Damit diese Speicherverwaltung nicht durcheinander gerät, dürfen Sie in eigenen Assembler-Routinen niemals einer BASIC-String-Variable einen Wert zuweisen!

Möchten Sie in eigenen Assembler-Routinen Zeichenketten erzeugen und diese im BASIC nutzen, dann geben Sie bitte die Anfangsadresse der Zeichenkette im HL-Register an das aufrufende BASIC-Programm zurück und wandeln dort mit der Funktion MEMSTR$ den Typ in String um. Nun können Sie z.B. diese Zeichenkette im BASIC einer String-Variable zuweisen.

Achtung! Wenn Sie in eigenen Assembler-Routinen einer Decimal-Variable einen Wert zuweisen, muss dieser Wert unbedingt ein gültiges BCD-Format haben! Anderenfalls kann es passieren, dass eine Division mit so einer ungültigen Zahl in einer Endlosschleife hängen bleibt.

3. Optimierer

Der BASIC-Compiler enthält einen Global-Optimizer, der den erzeugten Assembler-Code durchgeht und unnötige Befehle entfernt. Dabei kann auch der mit ASM-Anweisungen und ASM-Funktionen erzeugte Assembler-Code verändert werden. Der Optimierer fasst aber nur solche Assembler-Zeilen an, bei denen die Befehls-Mnemonik durch einen Tabulator vom Zeilenanfang bzw. der Marke getrennt ist. Wenn sie also sichergehen wollen, dass der Optimierer Ihren Assembler-Code nicht verändert, dann verwenden Sie keine Tabulatoren in den ASM-Anweisungen!