Im Magazin „MC" wurde im Juni 1986 der folgende Artikel abgedruckt.
Erzeugung von Inline-Dateien für Turbo Pascal 3.01.
|
|
Assembler-Routinen in Turbo-Pascal
Ein Programm zur Erzeugung von Inline-Code
Turbo-Pascal bietet mit dem Inline-Statement eine bequeme Möglichkeit, Maschinenspracheprogramme in ein Pascal-Programm einzubinden.
Bei umfangreicheren Routinen ist dabei jedoch von Nachteil, daß die Codierung von Hand erfolgen muß.
Abhilfe schafft hier ein Programm, das aus Relocatable-Code, wie er von Microsofts Macro-80-Assembler erzeugt wird, Inline-Code erzeugt, der über die $I-Compiler-Option oder mit Hilfe des Editors in ein Pascal-Programm eingebaut werden kann.
Macro-80 erzeugt aus dem Assembler-Quelltext eine Datei (mit der Typbezeichnung .REL), die sogenannten verschiebbaren Code enthält.
In dieser Datei sind Sprung- und Datenadressen, die beim Assemblieren noch nicht festliegen, als relative Adressen gekennzeichnet.
Der zum Assembler gehörende Linker (L80
) berechnet dann hieraus die absoluten Werte und erzeugt eine lauffähige COM-Datei.
Außerdem enthält die REL-Datei alle Informationen über externe oder globale Symbole, also etwa Adressen, über die verschiedene Module, die mit L80
zusammengebunden werden sollen, miteinander kommunizieren.
Damit enthält aber diese REL-Datei auch alle Informationen, die für die Erzeugung von Inline-Code für Turbo-Pascal benötigt werden.
Variablen, über die die Maschinencode-Routine mit dem Pascal-Programm kommunizieren soll, werden im Assembler-Programm als external deklariert.
Das hier vorgestellte Pascal-Programm PMLink
erzeugt dann aus dem REL-Code Inline-Code (in einer Datei mit Typ .INL), der die als external deklarierten Namen an den entsprechenden Stellen enthält.
Der Turbo-Pascal-Compiler setzt dann an den jeweiligen Stellen die Adressen der Variablen ein.
Bild 1 zeigt eine kurze Assembler-Routine, die ihr Argument
Word
um
NBits
Bits nach links rotiert und den so erhaltenen Wert im HL-Register als Funktionswert übergibt.
.z80
external Word,NBits
rotlft: ld HL,(Word)
ld A,(NBits)
and 0F
or A
ret Z ;kein Rotieren,
;wenn NBits=0
ld B,A
ld A,L
ld L,0
loop: sla A ;MSB im Carry
rl H ; "
adc A,L ;Carry wiedereinsetzen
djnz loop
ld L,A ; Ergebnis in HL
ret
end rotlft
|
Bild 1. Assembler-Quelle für die Funktion ROTLFT, die ihr integer-Argument Word um NBits nach links rotiert |
Word
und
NBits
werden im Assembler-Programm als external deklariert und erscheinen wieder im Inline-Code, der von
PMLink
erzeugt wurde (
Bild 2).
type Bits = 0..15;
function RotLft (Word: integer; NBits: Bits): integer;
begin {Modul ROTLFT}
InLine (
{0000} $2A/WORD /$3A/NBITS /$E6/$0F/$B7/$C8/$47/$7D/$2E/$00/$CB/$27/
{0010} $CB/$14/$8D/$10/$F9/$6F/$C9)
end; {ROTLFT}
|
Bild 2. Inline-Code zu ROTLFT; der Prozedurkopf wurde von Hand eingegeben |
Der Kopf des Unterprogramms muß natürlich von Hand eingesetzt werden;
alles weitere ab
begin
wird von
PMLink
produziert.
Der Aufbau von PMLink
Das Programm ist in
Bild 3 gezeigt.
|
[Listing] |
Bild 3. Programmlisting von PMLink.
Die in Bild 4 und 5 gezeigten Hilfsroutinen werden über die Include-Option eingelesen |
Es benötigt zwei Durchläufe;
im ersten (
FirstPass
) wird die REL-Datei eingelesen und eine interne Darstellung des Programms (
Prog
) erstellt, die zu jedem Byte Angaben über seine Bedeutung enthält und eine Symbol- und eine Offset-Tabelle umfaßt.
Letztere wird benötigt, um Ausdrücke von der Form
ld A,(Num+5)
verarbeiten zu können (wobei
Num
als external deklariert wurde).
Der entsprechende Inline-Code (
$3A/NUM+5/
) wird allerdings erst ab Turbo-Pascal-Version 3.0 verarbeitet - bei älteren Versionen sollten also Offsets keine Verwendung finden.
Im zweiten Durchlauf wird entsprechend der internen Darstellung der Inline-Code erzeugt und auf die INL-Datei ausgegeben.
Die Angabe der Dateinamen erfolgt interaktiv, wobei mehrere REL-Files nacheinander bearbeitet werden können.
Eine leere Eingabe für das REL-File beendet das Programm, während bei einer leeren Eingabe für das Inline-File dessen Name aus dem der REL-Datei erzeugt wird.
Die Typbezeichnungen .REL und .INL werden automatisch ergänzt, sofern keine Typen explizit angegeben werden.
Zu PMLink gehören noch die Module
PMLINK.UTL
(
Bild 4),
|
[Listing] |
Bild 4. Einige von PMLink benötigte Hilfsroutinen, u.a. zur Eingabe der Dateinamen und zur Hexadezimaldarstellung von Zahlen |
das einige Hilfsroutinen enthält, und
PMLINK.BIT
(
Bild 5),
|
[Listing] |
Bild 5. Die Routinen zum bitweisen Lesen der REL-Datei |
das die Grundlage für ein bitweises Einlesen der REL-Datei schafft.
Der von M80 erzeugte Code ist nämlich im Bitstrom organisiert, d.h.
die einzelnen Elemente (Items) beginnen und enden nicht an Bytegrenzen.
Das jeweils erste (höchstwertige) Bit eines Elements gibt an, ob es sich bei den folgenden acht Bits um ein absolut einzusetzendes Byte handelt.
Hat dieses Bit den Wert 1, so entscheiden statt dessen die nächsten zwei Bits über das weitere Schicksal der folgenden Bits usw.
(Einzelheiten hierüber findet man im Macro-80 Reference Manual von Microsoft, Anhang D
[oder hier]).
Durch diesen Aufbau im Bitstrom wird die REL-Datei klein gehalten, und es werden die Diskettenzugriffe beim Lesen minimiert.
Wie die Routinen in
PMLINK.BIT
zeigen, ist bitweises Lesen in Turbo-Pascal recht einfach zu verwirklichen.
Ein umfangreicheres Beispiel
Bild 6 zeigt eine Version der Funktion
GetBits
in Z80-Assembler.
.z80
external NBits,BitCnt,WB,InByte
getbts: ld HL,0 ;Ergebnis-Register
ld A,(NBits)
ld C,A ;C := NBits
ld A,(BitCnt)
cp C
jr NC,ok ;BitCnt>=NBits
ld B,A ;B := BitCnt
ld A,C ;A := NBits
sub B ;A : = NBits - BitCnt
ld (NBits),A
ld A,(WB)
call shl ;WB shl BitCnt
push HL ;Zwischenergebnis
call InByte ;neues Byte in L
ld A,(NBits)
ld B,A ;B := NBits
ld A,8 ;
sub B ;A := 8 - NBits
ld (BitCnt),A
ld A,L ;neues Byte
pop HL
call shl ;WB shl NBits
ld (WB),A
ret
ok: ld B,C ;B := NBits
sub B ;A := BitCnt - NBits
ld (BitCnt),A
ld A,(WB)
call shl
ld (WB),A
ret
shl: sla A ;MSB in Carry
rl L
djnz shl
ret
end getbts
|
Bild 6. Eine Version der Funktion GetBits in Assembler |
PMLink
kann nun dazu verwendet werden, um sich selbst etwas effektiver zu machen (der Geschwindigkeitsvorteil wirkt sich allerdings nur bei der Verwendung von RAM-Disks aus, da im anderen Fall die Diskettenzugriffe die Arbeitsgeschwindigkeit des Programms bestimmen).
Der mit
PMLink
erzeugte entsprechende Inline-Code ist in
Bild 7 gezeigt.
function GetBits (NBits: Bits): byte;
begin {Modul GETBTS}
inline (
{0000} $21/$00/$00/$3A/NBITS /$4F/$3A/BITCNT /$B9/$30/$23/$47/$79/$90/
{0010} $2/NBITS /$3A/WB /$CD/*+40 /$E5/$CD/INBYTE /$3A/NBITS /
{0020} $47/$3E/$08/$90/$32/BITCNT /$7D/$E1/$CD/*+21 /$32/WB /$C9/
{0030} $41/$90/$32/BITCNT /$3A/WB /$CD/*+6 /$32/WB /$C9/$CB/
{0040} $27/$CB/$15/$10/$FA/$C9)
end; {GETBTS}
|
Bild 7. Inline-Version von GetBits. Der Aufruf von InByte wird in dieser Form erst ab Turbo-Pascal Version 3.0 verarbeitet |
Hieraus wird eine Einschränkung erkennbar:
Alle in der REL-Datei enthaltenen Namen (also Programmname und als external deklarierte Symbole) dürfen nicht mehr als sechs Zeichen enthalten.
Aus diesem Grund wurde der Name des Unterprogramms auf GETBTS
gekürzt und darauf geachtet, daß die Variablen- und Funktionsnamen, die GETBTS
benutzt, nicht mehr als sechs Zeichen lang sind.
GETBTS
zeigt unter anderem, daß es auch möglich ist, Prozeduren und Funktionen, die in Pascal definiert worden sind, aus einer Maschinensprache-Routine aufzurufen - wie hier die Funktion InByte
.
Allerdings werden solche Aufrufe erst ab Turbo-Pascal-Version 3.0 verarbeitet;
ältere Versionen des Compilers brechen mit einer Fehlermeldung ab.
Diese Einschränkung kann aber leicht umgangen werden:
Man plaziere in diesem Fall unmittelbar vor die aufzurufende Pascal-Routine die Deklaration einer typisierten Byte-Konstante mit dem Wert 0, in unserem Beispiel vor InByte
das Statement
const InBtAd: byte = 0,
und setze in der Maschinensprache-Routine den Namen dieser Konstante anstatt des Namens der Pascal-Routine ein
(also call InBtAd
statt call InByte
, mit der entsprechenden external-Deklaration).
Bei der Ausführung des call-Befehls trifft nun der Prozessor auf die Speicherzelle, in der das Byte mit dem Wert 0 abgelegt ist;
dieser Wert wird als Opcode für NOP
interpretiert und übersprungen, wodurch schließlich die gewünschte Prozedur erreicht wird.
Was noch zu beachten ist
Bei der Erstellung des Assembler-Codes ist darauf zu achten, daß sich hieraus sinnvoller Inline-Code erzeugen läßt;
das Assemblerprogramm sollte z.B. keine aseg
-, dseg
-, common
- oder .phase
-Pseudo-Opcodes enthalten.
Ebenso wenig ist es sinnvoll, Symbole als public
zu deklarieren, da PMLink
keinen Maschinensprache-Linker darstellt, d.h. es ist nicht möglich, vor der Erzeugung des Inline-Codes wie mit L80
verschiedene Assembler-Routinen zusammenzubinden.
Allerdings können mehrere Module vor der Bearbeitung mit PMLink
mit LIB80
zusammengefaßt werden;
die Module erscheinen im Inline-Text durch begin
und end
eingefaßt und sind durch ihre Namen in Kommentarklammern gekennzeichnet.
Alle Items, die von PMLink
nicht verarbeitet werden, führen zu einer entsprechenden Fehlermeldung;
dabei wird das Programm nicht abgebrochen, sondern der Bitstrom korrekt weiterverarbeitet — der erzeugte Inline-Code ist jedoch nicht unbedingt richtig.
Bezüglich der Arbeitsweise des Pascal-Compilers wäre noch anzumerken, daß Funktionen, die ihre Werte unmittelbar im HL-Register übergeben, mit einem ret
-Opcode abzuschließen sind, da der Compiler am Ende der Funktion noch Code ergänzt, durch den das HL-Register aus einer speziellen Speicherzelle geladen wird.
Dieser Code sollte natürlich nicht zur Ausführung gelangen, wenn das Register bereits den Funktionswert enthält.
Bei Prozeduren ist ein explizites ret
nicht notwendig.
Arbeitet eine Maschinensprache-Routine mit var
-Parametern, so ist zu beachten, daß der Wert, der unter der entsprechenden Adresse abgelegt ist, nicht den Wert der Variablen darstellt, sondern angibt, unter welcher Adresse dieser Wert zu finden ist — im Gegensatz zu Parametern, deren Wert unmittelbar übergeben wird, und Variablen, die innerhalb einer Prozedur oder Funktion deklariert worden sind.
Im Original-Programm ist es nicht möglich, den Programmzähler über die Assembler-Anweisung DS zu setzen.
Ich fand, dass auch weitere Verschlimmbesserungen angebracht seien und habe
hier eine auf den Joyce-Bildschirm abgestimmte Version daraus entwickelt.
|
[April 2007]
Auf der Webseite von Hans Otten findet sich in der Rubrik
„Pascal on CP/M MSX" in der Rubrik „Pascal articles" das INLINE program by J.A.C.G. van der Valk.
Dieses Programm verarbeitet ebenfalls das .REL-80 Format.
Es liegt dort vor in Form von eingescannten Seiten sowie als Textdatei (OCR der gescannten Seiten).
Hier als Quelle INLINE.PAS
|
Eingescanned von
Werner Cirsovius
März 2003, April 2007, 2010
© Franzis' Verlag