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.
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.
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.
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!