Möchten Sie Pascalprogramme (z.B. aus Geschwindigkeitsgründen) um Unterprogramme in Maschinensprache erweitern, so benötigen Sie (neben einem Assembler) genauere Kenntnisse über die Speicherverteilung bei übersetzten Pascalprogrammen und die Darstellung von Variablen im Hauptspeicher. Diese Themen werden jeweils in den Abschnitten 4.3.4 und 4.4.2 behandelt.
Neben der Möglichkeit Registerinhalte beim Aufruf von Maschinencodeprogrammen zu übergeben (s. Beschreibung des Befehls SYS in Kapitel 4.4.4.8) können Sie Variablen auch an absolute Adressen in Bank 1 und der common area binden (s. Abschnitt 4.4.2 »absolut adressierte Variablen«). Schließlich können Sie mit der Funktion ADR die absolute Adresse einer Variablen auf dem stack und dem heap bestimmen.
Durch Programmparameter (s. Abschnitt 4.3.4) können Sie praktisch überall im Speicher Platz für Maschinenroutinen reservieren, die Sie beim Programmstart von Diskette nachladen können. Besonders benutzerfreundlich ist die folgende Methode, bei der der Maschinencode zusammen mit dem Pascalcode in Bank 0 gespeichert wird, so daß er mit den BASIC-Befehlen LOAD und SAVE automatisch mitgeladen wird.
Als Beispiel soll das folgende kleine Assemblerprogramm aufgerufen werden:
$1CE9 JSR $FF7D (Aufruf der Routine PRIMM) DFD 'HALLO WELT' , 0 RTS (Rückkehr zu PASCAL)
Es beginnt 10 Bytes hinter dem Ende des Startcodes eines Pascal-Programmes ($1CE9 = 7391 + 10) und endet bei der Adresse $1CF8 = 7391 + 10 + 15.
Den obigen Code können Sie direkt mit dem Monitor eingeben, oder von einem assemblierten File absolut laden.
Dieses Mini-Assemblerprogamm soll von dem folgenden Pascal-Programm aufgerufen werden:
PROGRAM MCODE (OUTPUT; 8192); CONST PRINT = 7401; (* Startadresse M-Code *) VAR I: INTEGER; BEGIN BANK:= 15; (* Code liegt in BANK 0 und benutzt Kernal *) FOR I:= 1 TO 5 DO SYS(PRINT); END.
Die Zahl 8192 im Programmkopf veranlaßt den Compiler, den erzeugten Code ab der Adresse 8192 zu speichern. Dadurch wird verhindert, daß der Maschinencode (ab der Adresse 7401) überschrieben wird. Am Ende der Compilation erhalten Sie folgende Meldung:
P-CODE FROM 8192 TO 8238 CONSTANTS FROM 8192 TO 8192
Das heißt, der Programmcode belegt den Speicherbereich zwischen 8192 und 8238 in Bank 0. Gleichzeitig hat der Compiler den Zeiger auf das Ende der BASIC-Programme auf die Adresse 8238 gesetzt.
Nun muß dem Startcode für Pascalprogramme noch die geänderte Anfangsadresse des P-Codes mitgeteilt werden (s. Abschnitt 4.3.4). Dies geschieht ebenfalls am einfachsten mit dem ROM-Monitor:
T +8192,+8200,+7391
Damit werden 8 Bytes ab der Adresse 7391 (dem normalen P-Code Start) gespeichert, um das Pascal-Laufzeitsystem von der Änderung der Speicherverteilung zu unterrichten.
Damit sind Pascal-Programm und Maschinencode zu einer Einheit verknüpft, die nun auf Diskette gespeichert werden kann:
SAVE"BOTH",8
Damit wird der Speicherbereich von 7169 bis 8238 gespeichert. Das Programm kann nun wie jedes Pascalprogramm von Diskette geladen werden und gestartet werden:
LOAD"BOTH",8 RUN
Nachdem Sie nun wissen, wie Sie Maschinenprogramme zusammen mit Pascalprogrammen speichern können, sollen die folgenden Beispiele die Parameterübergabe zwischen den Routinen erläutern.
FUNCTION FREEMEM: INTEGER; VAR TOPOFSTACK : INTEGER [47]; BOTTOMOFHEAP: INTEGER [53]; BEGIN FREEMEM:= ADDU (BOTTOMOFHEAP, - TOPOFSTACK); END; (* FREEMEM *)
Die beiden Variablen TOPOFSTACK und BOTTOMOFHEAP werden vom Pascal-Laufzeitsystem verwaltet und markieren das untere und obere Ende des freien Speicherbereiches in Bank 1 zwischen stack und heap. Die Größe dieses Speicherbereiches (in Bytes) erhält man also einfach durch die Subtraktion der Adressen. Dabei wird die Funktion ADDU verwendet, um Bereichsüberschreitungen des Typs INTEGER zu ignorieren.
Die Kommunikation mit absoluten Variablen erfolgt somit nach dem folgenden Schema: Im Pascalprogramm deklariert man eine Variable eines geeigneten Typs, die an eine absolute Adresse gebunden wird. Im Maschinencode kann man die dort gespeicherten Daten lesen und modifizieren:
PUFFER EQU $FE STARTINC PUFFER BNE NOINC INC PUFFER+1 NOINC RTS
Diese Routine erhöht also bei jedem Aufruf den Inhalt der 2-Byte Variablen Puffer an der Adresse $FE/$FF um 1. Zum Aufruf können Sie folgendes Programm verwenden:
PROGRAM CALLIT (OUTPUT); CONST START = ....; (* Anfangsadresse M-Code *) VAR PUFFER[254]; (* = $FE *) BEGIN READLN(PUFFER); SYS(START); WRITELN(PUFFER); PUFFER:= 2 * PUFFER; SYS(START); WRITELN(PUFFER); END.
Absolut adressierte Variablen können ohne Einschränkungen in Funktionen, Prozeduren und Ausdrücken verwendet werden. Das folgende Programm modifiziert den Heappointer, um auf dem heap einen Speicherbereich für N-Bytes zu reservieren:
PROCEDURE GETMEM(N: INTEGER); VAR BOTTOMOFHEAP: INTEGER [53]; BEGIN IF FREEMEM<N THEN BEGIN WRITELN('OUT OF MEMORY ERROR'); HALT END ELSE BOTTOMOFHEAP:= ADDU(BOTTOMOFHEAP, -N); END; (* GETMEM *)
Insbesondere beim Aufruf von ROM-Routinen erfolgt die Parameterübergabe an Unterprogramme in Maschinensprache über Werte in den Prozessorregistern. Daher können Sie in Pascal 2.0 den SYS-Befehl um eine Registervariable erweitern.
START INX DEY LDA #0 SEC RTS
Dieses Unterprogramm erhöht das X-Register um 1, decrementiert das Y-Register, löscht den Akkumulator und setzt das Statusregister. Diese Änderungen der Prozessorregister lassen sich mit dem folgen- den Pascalprogramm nachvollziehen:
PROGRAM REGISTER (INPUT,OUTPUT); CONST START = ...; (* Startadresse der Routine *) VAR REGS: RECORD SR : BYTE; (* STATUSREGISTER *) AKKU: BYTE; (* AKKUMULATOR *) X : BYTE; (* X-REGISTER *) Y : BYTE; (* Y-REGISTER *) END; BEGIN WITH REGS DO BEGIN WRITE('AKKU :'); READLN(AKKU); WRITE('X :'); READLN(X); WRITE('Y :'); READLN(Y); END; SYS(START,REGS); (* Register werden verändert *) WITH REGS DO BEGIN WRITE('AKKU :', AKKU:4); WRITE('X :', X :4); WRITE('Y :', Y :4); IF ODD(SR) THEN WRITE('Carry '); IF (SR AND 2)<>0 THEN WRITE ('Zero '); IF (SR AND 4)<>0 THEN WRITE ('IRQ '); ... IF (SR AND 128)<>0 THEN WRITE ('Negative '); END; END.
Bild 106: Parameterübergabe mit Registervariable
Natürlich existiert auch in Pascal 2.0 die Möglichkeit, Parameter explizit mit POKE- und PEEK-Befehlen zu bearbeiten. Das folgende Programm verwendet z.B. die im C-128 interruptgesteuert arbeitende 48-Bit Uhr:
PROGRAM TIME (INPUT,OUTPUT); CONST TI = 160; (* 1/60 Sekunden Echtzeituhr *) BEGIN BANK:= 15; POKE(TI,0); POKE (TI+1,0); POKE(TI+2,0); REPEAT WRITELN(PEEK(TI):4,PEEK(TI+1):4, PEEK(TI+2):4) UNTIL PEEK(TI+1) = 5; END.
Natürlich ist der Bildschirm auch in Pascal 2.0 nicht vor hüpfenden Bällen sicher, die jedoch (trotz REAL-Arithmetik!) deutlich schneller als in BASIC fliegen. Vielleicht ist es recht eindrucksvoll, wenn Sie zum Vergleich die Variablen XK, YK sowie XSPEED und YSPEED als INTEGER-Variablen deklarieren (die Funktion INT wird dann natürlich nicht mehr bei der Berechnung von POS benötigt).
PROGRAM BOUNCE (OUTPUT); CONST B = 40; (* Bildschirmbreite und -Höhe *) H = 25; BLANK = 32; (* Bildschirmcode für Leerzeichen *) BALL = 81; SCREENBASE = 1024; (* Basisadresse Video RAM *) VAR XK,YK : REAL; XSPEED, YSPEED: REAL; POS,LASTPOS : INTEGER; BEGIN SCNCLR; DISPLAY(1,10,10,' ',1); BANK:= 0; XSPEED:= RANDOM(0)*4; YSPEED:= 0.75; XK := B DIV 2; (* Bildmitte *) YK := H DIV 2; LASTPOS:= SCREENBASE; REPEAT XK:= XK + XSPEED; YK:= YK + YSPEED; IF XK>=B THEN BEGIN XK:= 2*B-XK; XSPEED:= -XSPEED END ELSE IF XK<0 THEN BEGIN XK:=-XK ; XSPEED:= -XSPEED END; IF YK>=H THEN BEGIN YK:= 2*H-YK; YSPEED:= -YSPEED END ELSE IF YK<0 THEN BEGIN YK:=-YK ; YSPEED:= -YSPEED END; POS:= SCREENBASE + INT(XK) + B * INT (YK); IF POS<>LASTPOS THEN BEGIN POKE(LASTPOS,BLANK); IF PEEK(POS)<>BLANK THEN BEGIN XSPEED:=-XSPEED; YSPEED:= -YSPEED; END; POKE(POS,BALL); LASTPOS:= POS END; UNTIL KEYPRESSED; POKE(LASTPOS, BLANK); END.
Bild 107: Adressierung des Bildschirmspeichers