home *** CD-ROM | disk | FTP | other *** search
-
- PASCOMP
-
- PASCAL baut einen Pascal-Compiler
-
- Teil 6: Die Code-Erzeugung
- von Johannes Velmans
-
- Mit der ausführlichen Betrachtung der semantischen Analyse haben
- wir in der letzten Folge dieser Serie die Analysephase des
- Compilers endlich hinter uns gebracht. Während dieser gesamten
- Analyse ist das Pascal-Quellprogramm sozusagen in seine
- "Einzelteile" zerlegt worden, wobei alle für ein Pascalprogramm
- wichtigen Komponenten, wie zum Beispiel Notation, Syntax und
- Semantik eingehend überprüft und auf ihre Richtigkeit hin
- untersucht worden sind.
-
-
- Nach dieser Analysephase folgt nun im Compiler die Synthesephase,
- deren Aufgabe darin besteht, die einzelnen Stückchen wieder zu
- einem für die benutzte Maschine verständlichen Programm zusammen-
- zusetzen. Dabei ist von besonderer Wichtigkeit, daß die mit die-
- sem Kapitel beginnende Synthese des Maschinenprogramms die Spezi-
- fikation und Aufgabenstellung des Pascal-Quellprogramms in kein-
- ster Weise verändert. Hinsichtlich der Codeerzeugung muß nicht
- notwendigerweise ausführbarer Code für eine real existierende
- Maschine erzeugt werden - es kann durchaus Sinn machen, Code für
- eine hardwaremäßig nicht existierende, virtuelle Maschine zu ge-
- nerieren. Dies mag dem einen oder anderen Leser vielleicht nicht
- sofort einleuchten, denkt er doch bei der Codegenerierung in er-
- ster Linie an die Geschwindigkeit des ausführbaren Programms.
-
- Bei genauerer Betrachtung erkennt man allerdings, daß die Compi-
- lierung auf eine Zwischensprache hin, bis auf den Nachteil der
- Geschwindigkeit, eigentlich nur wesentliche Vorteile gegenüber
- der maschinenbezogenen Codeerzeugung vorzuweisen hat: Da wäre zum
- einem der "kontrollierte Ablauf" des Zwischensprache-Programms zu
- nennen, der gegenüber der direkten Codeerzeugung den Vorteil hat,
- das Programm während der Ausführungszeit schrittweise ablaufen zu
- lassen und somit die Qualität für etwaige Testprogramme deutlich
- zu erhöhen.
-
- Solche Tracing-Möglichkeiten sind den BASIC-Programmieren unter
- den Lesern sicherlich schon bestens bekannt. Zum anderen bietet
- die Hardware- Unabhängigkeit der Zielsprache den Vorteil, bei der
- Übersetzung des Programms die spezifischen Eigenschaften des be-
- nutzten Prozessors, deren Berücksichtigung wiederum mit einem
- Mehraufwand bei der Codeerzeugung verbunden ist, völlig außer
- Acht zu lassen.
-
- Diese Maschinen-Unabhängigkeit bedingt somit eine Erhöhung der
- Portabilität des eigentlichen Pascal-Programms, was auch im Sinne
- des Erfinders war. Mit Pascal sollte nämlich keine Programmier-
- sprache konstruiert und formuliert werden, die für einen bestimm-
- ten Rechnertyp spezifisch ist, sondern die neu zu schaffende
- Sprache sollte vielmehr einen "universellen Charakter" besitzen.
- Gerade dies ist, wie man heute sagen kann, auch gelungen, denn es
- gibt wohl kaum einen Rechner, für den nicht irgendwann einmal
- eine Pascal-Version implementiert wurde.
-
- Um einen tieferen Einblick in die Zwischensprache zu gewinnen,
- werden wir uns im nachfolgenden etwas näher auf die theoretische
- Maschine, die unseren Zwischencode akzeptiert, konzentrieren.
-
-
- Die P-Code Maschine
-
- Die virtuelle P-Code Maschine ist vom Prinzip her vergleichbar
- mit einem konventionellen Computer: Sie besteht in groben Zügen
- aus einem Prozessor und einem Speicher. Der hauptsächliche Unter-
- schied zu einem herkömmlichen Rechner besteht in einem "Mangel"
- an Hardware-Registern. Es existieren zwar einige interne Regi-
- ster, die aber nicht zur freien Verfügung des Benutzers stehen.
- Diese Tatsache hat eine direkte Auswirkung auf die Art des Pro-
- zessor-Befehlssatzes. Hier finden wir weniger Befehle, die sich
- auf spezielle Hardware-Register beziehen, sondern eher solche
- Befehle, die eine Auswirkung auf den internen Stack der Maschine
- haben, der einen Teil des Speichers darstellt.
-
- So werden zum Beispiel Parameter einer Prozedur bei deren Aufruf
- nicht in "schnelle" Register abgelegt, sondern grundsätzlich über
- den Stack übergeben. Im weiteren werden Rückkehradressen oder
- Funktionsergebnisse ebenfalls nicht in Registern, sondern immer
- über den internen Stack übergeben. Die einzelnen Maschinenbefeh-
- le, die im folgenden zum Begriff des P-Codes zusammengefaßt wer-
- den, sind im Speicher untergebracht. Auf sie kann in üblicher Art
- und Weise zugegriffen werden. Der Prozessor selbst hat, wie jeder
- andere Prozessor auch, einen definierten Befehlssatz. Die Anzahl
- der Prozessor-Register ist auf fünf beschränkt. Die Register ha-
- ben im speziellen die folgende Bezeichnung und Bedeutung:
-
- Programmzähler (PC):
- Der PC stellt einen Zeiger auf den nächsten auszuführenden Befehl
- dar.
-
- Stackpointer (SP):
- Der SP verweist auf die jeweils aktuelle Spitze des Stacks.
-
- Markstackpointer (MP):
- Der MP dient zur Kennzeichnung der aktuellen Laufzeitschachtel.
-
- Newpointer (NP):
- NP zeigt auf das nächste freie Element im Heap. Der Heap verwal-
- tet, wie bereits in den vorherigen Folgen erläutert, dynamischen
- Speicherplatz, der während der Laufzeit des Programms angefordert
- und wieder freigegeben werden kann.
-
- Extreme-Pointer (EP):
- EP stellt wie SP ein Verweis in den Stack dar. Die Stelle, auf
- die er zeigt, ist eine maximale Grenze von SP, die nicht über-
- schritten wird, wenn die zugehörige Prozedur aufgerufen wird (lo-
- kaler Stack). Wie werden nun Stack und Heap organisiert) Sieht
- man den gesamten Speicher als ein großes eindimensionales Array
- an, so legt man einfach fest, daß der Stack an der unteren Grenze
- und der Heap an der oberen Grenze dieses Arrays beginnt. Der
- Stack wächst also mit steigenden Adressen, der Heap wächst mit
- fallenden Adressen. Verwaltet wird der Heap durch die Standard-
- Funktionen New (neuer Speicherplatz wird angefordert) und Mark
- und Release (benutzter Speicherplatz wird wieder freigegeben).
-
- Der Stack besitzt jedoch noch eine weitere interne Struktur. Er
- dient dort in erster Linie zur Aufnahme von sogenannten "Stack-
- frames" (Laufzeitschachteln). Zur Erinnerung: Eine Laufzeit-
- schachtel wird erzeugt und auf dem Stack untergebracht, wenn eine
- benutzerdefinierte Funktion oder Prozedur aktiviert wird. Um die
- Codeerzeugung zu vereinfachen, wurde für Prozeduren und Funktio-
- nen der gleiche Laufzeitschachtel-Aufbau gewählt. Eine besondere
- Erwähnung findet an dieser Stelle die Laufzeitschachtel des
- Hauptprogramms, deren Aktivierung nicht explizit geschieht, son-
- dern mit dem Start des Programms stattfindet.
-
- Der Markstackpointer (MP) verweist auf den Anfang der obersten
- auf dem Stack liegenden Laufzeitschachtel. MP hat zwei Funktio-
- nen: Zum einen reorganisiert er den Stack, wenn die Ausführung
- einer Prozedur oder Funktion beendet wird. Zum anderen liefert er
- die Basisadresse, mit deren Hilfe die Adressen von lokalen Varia-
- blen gewonnen werden können. Der Extreme-Stackpointer EP verweist
- dagegen auf die Spitze der aktuellen Laufzeitschachtel. Durch
- einen Vergleich von EP und MP ist es während der Laufzeit mög-
- lich, festzustellen, ob der Stack in den Heap oder der Heap in
- den Stack überläuft. Durch diese Konstruktion wird eine Stack-
- pointer-Überprüfung bei der sehr häufig auftretenden Inkrementie-
- rung des Stackpointers überflüssig, was wiederum eine Geschwin-
- digkeitsverbesserung während der Laufzeit zur Folge hat.
-
- Da die maximale Größe eines Stackframes (Laufzeitschachtel) wäh-
- rend der Übersetzungszeit bereits bekannt ist, geht ihr Wert in
- EP mit ein. Der lokale Stack wird benötigt, um temporäre Werte
- während der Ausführung einer Prozedur oder Funktion aufzunehmen.
- Temporäre Werte können Werte von lokalen Variablen sein oder Wer-
- te von Hilfsvariablen, die für den internen Ablauf von Kontroll-
- strukturen (for-, while-Schleifen) benötigt werden.
-
- Zur näheren Erläuterung sei die Auswirkung eines speziellen P-
- Code-Befehls auf den Stack betrachtet. Als Beispiel wählen wir
- den sbi-Befehl. sbi nimmt zwei Werte vom Stack und legt das Er-
- gebnis der Subtraktion wieder auf den Stack zurück. Der Stack-
- pointer wird bei dieser Operation um den Wert 1 dekrementiert.
-
- Für das weitere Verständnis des P-Codes ist es erforderlich, sich
- einen Einblick in die Organisation der Laufzeitschachtel zu ver-
- schaffen. Mit Organisation ist hier der Vorgang gemeint, in wel-
- cher Weise bei einem Aufruf einer Prozedur die Laufzeitschachtel
- auf dem Stack aufgebaut wird. Als Beispiel soll hier ein Aus-
- schnitt aus einem Pascal-Programm dienen:
-
- ...
- PROCEDURE a;
- PROCEDURE b;
- PROCEDURE c;
- BEGIN
- ... (* statischer Vorgänger: b *)
- END;
- PROCEDURE d;
- BEGIN ...
- (* statischer Vorgänger: b *)
- END;
- BEGIN (* b *)
- ... * statischer Vorgänger: a *)
- END;
- Begin (* a *)
- ...
- END;
- ...
-
- Mit Hilfe des statischen Vorgängers ist es möglich, während der
- Übersetzung die Anfangsadressen von lokalen Variablen in textuell
- vorher definierten Blöcken zu gewinnen. Das bedeutet, daß es über
- den statischen Vorgänger möglich ist, alle gültigen Definitionen
- von Variablen in einem bestimmten und - bezogen auf die gerade
- betrachtete Prozedur - gültigen Block herauszufinden.
-
- Der dynamische Vorgänger verweist auf die Laufzeitschachtel
- derjenigen Prozedur, die die gerade aktive Prozedur aufgerufen
- hat. Über das Zusammenspiel von statischen und dynamischen
- Vorgängern soll das nächste, rekursive Beispiel einen Einblick
- vermitteln. Dazu sei das folgende Pascal-Programm gegeben:
-
- PROGRAM hauptprogramm;
- VAR j : INTEGER;
- PROCEDURE p;
- VAR i : INTEGER;
- PROCEDURE q;
- BEGIN
- IF i > 0 THEN BEGIN
- i := Pred(i);
- j := Succ(j);
- q; (* rekursiver Aufruf von q *)
- END
- ELSE Write(0)
- END; (* q *)
- BEGIN (* p *)
- i := 2;
- q;
- END; (* p *)
-
-
-
- BEGIN (* hauptprogramm *)
- j := 0;
- p;
- END.
-
-
- Dieses Progrämmchen bewirkt eine Laufzeitkeller-Konfiguration.
-
-
- Aufbau einer Laufzeitschachtel
-
- Es gibt drei P-Code Befehle, die eine Laufzeitschachtel komplett
- aufbauen. Dieses sind die Befehle mst, cup und ent. Die Bedeutung
- der Befehle im einzelnen:
-
- mst:
- /Mark Stack). Der Mark-Stack-Befehl markiert die aktuelle Posi-
- tion im Stack, die dann später den Anfang der neuen Laufzeit-
- schachtel darstellen wird. Als Parameter wird mst eine Integer-
- Zahl k übergeben, die ein Indikator für die Blockschachtelungs-
- tiefe der Prozedur ist. k berechnet sich dabei folgendermaßen:
-
- k := 1 +
- (Blockschachtelungstiefe
- der aufrufenden Prozedur)
-
- (Blockschachtelungstiefe
- der aufgerufenen Prozedur)
-
- Die Ausführung von mst im Interpreter löst eine Berechnung der
- Adressen für den statischen und dynamischen Vorgänger aus und
- rettet den Extreme-Stackpointer EP für spätere Zwecke. Sind der
- statische und dynamische Vorgänger bestimmt, so wird der Stack-
- pointer SP soweit inkrementiert, bis er auf den Bereich für die
- Parameter verweist. An dieser Stelle trifft man dann in der P-
- Code-Sequenz auf eine Code-StÜck, das die Pr ozedurparameter auf
- den Stack lädt. Die typische Sequenz einer Laufzeitschachtel-
- Konstruktion läßt sich dann wie folgt darstellen:
-
- mst 0
- [Code, um Parameter auf den Stack zu laden]
- cup 0 3
-
- cup:
- "Call User Procedure". Dieser Befehl bewirkt, daß MP auf den Be-
- ginn der neuen Laufzeitschachtel gesetzt und die Rückkehradresse
- bestimmt wird. Schließlich ruft cup einen Sprung zu der durch den
- zweiten Parameter angegebenen Marke hervor. Durch den ersten Pa-
- rameter wird an die aufzurufende Prozedur eine Information über
- die Größe der Parameter in "Speicherplätze" gegeben. Ist der cup-
- Befehl beendet, so verweist der Programmzähler PC auf den Anfang
- der auszuführenden Prozedur.
-
-
- ent:
- "Enter User Defined Procedure". Diesen Befehl findet man gleich
- zweimal zu Beginn einer jeden benutzerdefinierten Prozedur. Die
- Operanden dieses Befehls definieren die Größe des Laufzeitkel-
- lers. ent besitzt zwei Parameter. Hat der erste Parameter den
- Wert eins, dann gibt der zweite Parameter den Speicherplatz an,
- der für lokale Variable benötigt wird. Der Stackpointer SP wird
- demzufolge entsprechend inkrementiert. Ist der Wert des ersten
- Parameters zwei , so gibt der Wert des zweiten Parameters die
- Größe des lokalen Stacks an und EP, der Extreme-Stackpointer,
- wird aktualisiert. An dieser Stelle wird dann während der Ausfüh-
- rungszeit durch den Interpreter eine Laufzeitüberprüfung veran-
- laßt. Diese Überprüfung entscheidet, ob der Stack in den Heap
- überläuft oder nicht.
-
- Die Laufzeitschachtel für das Hauptprogramm wird in der gleichen
- Weise gehandhabt - mit der Ausnahme, daß vier Speicherplätze für
- Filezugriffe auf die Standardfiles Input und Output fest
- reserviert sind. Diese vier Speicherplätze sind wie folgt
- organisiert:
- Jeweils zwei Speicherplätze gehören zu einer Textdatei. Davon ist
- der erste Speicherplatz der Filepuffer und der zweite Speicher-
- platz das Filehandle. Das Filehandle kennzeichnet die Datei in-
- tern im Interpreter, wozu ihr eine Nummer zur Verwaltung zugeord-
- net wird. Für das Standardfile Input ist das Filehandle als -4
- definiert. Die Datei Output erhält das Filehandle -3 als Konstan-
- te zugewiesen. Der Wertebereich des Filehandles beschränkt sich
- auf den Bereich von -4 bis 4 (ohne 0), so daß eine maximale An-
- zahl von acht offenen Dateien nicht überschritten werden darf.
- Ein Wert kleiner als Null kennzeichnet ein Textfile, ein Wert
- größer Null kennzeichnet ein Nicht-Textfile.
-
-
- Codeerzeugung für Statements
-
- Es existieren in der Pascal-Syntax neun verschiedene Arten von
- Statements: if-, else-, while-, repeat-, for-, with-, goto-, com-
- pound-, assignment- und procedure-call-Statements. Auf welche Art
- und Weise nun für die einzelnen Statements Code erzeugt wird,
- soll hier kurz dargestellt werden:
-
- Assignments:
- Für Zuweisungen benutzt man drei verschiedene Code-Stücke, die je
- nach der Beschaffenheit der Zuweisung verwendet werden. Im ein-
- fachsten Fall wird direkt ein Wert an eine Variable eines einfa-
- chen Typs, also kein Array oder Record, zugewiesen:
- a :=0
-
- a soll hierbei eine Variable sein. Der erzeugte P-Code ist dann
- folgendermaßen organisiert:
-
- LDCI 0
- bringe die Konstante 0 auf den Stack.
- STRI 0 5
- Speichere das oberste Element des Stacks in der aktuellen
- Schachtel (Blockschachtelungstiefe = 0) bei Relativadresse 5
- (Adresse von a) ab.
-
- Ist a aber eine globale, also eine im Hauptprogramm definierte
- Variable, so wird das folgende P-Code-Stück erzeugt:
-
- LDCI 0
- Bringe die Konstante 0 auf den Stack.
- STOI 10
- Speichere das oberste Element des Stacks in a ab. Die Variable a
- ist dabei in der Hauptprogramm-Schachtel mit der Relativadresse
- 10 definiert.
-
- Der nächste mögliche Fall einer Zuweisung tritt dann ein, wenn
- die Variable von einem einfachen Typ ist, die Zuweisung aber
- indirekt über einen Zeiger oder über eine formale Variable
- erfolgt. Ein Beispiel hierfür wäre etwa:
- p^ := 0 oder f := 0 wobei p und f lokal definiert sind. Die vom
- Compiler erzeugte P-Code-Sequenz sieht dann so aus:
-
- LODA 0 5
- Bringe die Adresse der Variablen auf den Stack.
- LDCI 0
- Bringe die Konstante 0 auf den Stack.
- STRI
- Speichere die Konstante indirekt an der auf dem Stack liegenden
- Adresse ab.
-
- Ist p global (f kann als formale Variable nicht global sein),
- dann ändert sich nur die Bestimmung der Variablenadresse:
-
- LDOA
- Die Variable ist im Hauptprogramm mit der Relativadresse 9 defi-
- niert. Bringe die Absolutadresse der Variablen auf den Stack.
- LDCI 0
- Bringe die Konstante 0 auf den Stack.
- STRI
- Speichere die Konstante indirekt an der auf dem Stack liegenden
- Adresse ab.
-
- Schließlich gibt es als dritten Zuweisungsfall noch die Möglich-
- keit, Variablen von zusammengesetzten Typen aneinander zuzuweisen
- (Arrays oder Records). Auch hier wiederum ein Beispiel:
-
- r := s
-
- r und s sollen auch hier lokal definiert sein. Da hier nun belie-
- bige Typen zugelassen sind, muß die Komponente des Speicherum-
- fangs der benutzten Variablen in die P-Codeerzeugung mit einge-
- hen:
-
- LDA 0 5
- Lade die Adresse von r auf den Stack.
- LDA 0 99
- Lade die Adresse von s auf den Stack.
- MOV 20
- Kopiere 20 Speicherplätze von s nach r.
-
- Sind r und s keine lokalen, sondern global definierte Variablen,
- so wird in der Codeerzeugung der Befehl LAO anstelle von LDA ver-
- wendet.
-
- Goto-Statements:
- Der P-Code für Goto-Statements besteht nur aus einem unbedingten
- Sprung zu einem angegebenen Label:
-
- UJP 5
- Springe ohne Bedingung zu Label 5.
-
- Compound-Statements:
- Compound-Statements erzeugen keinen eigenständigen P-Code, son-
- dern "verwalten" nur eine Liste von Statements, was man der Pas-
- cal-Syntax leicht entnehmen kann.
-
- If-Statements:
- Der Code für ein if-Statement
-
- If Bedingung then Statement1
- else Statement2
-
- wird folgendermaßen erzeugt:
-
- [ P-Code für die Bedingung ]
- FJP 6
- [ P-Code für Statement1 ]
- UJP 7
- L 6
- [ P-Code für Statement2 ]
- L 7
-
- Fehlt der else-Teil im if-Statement, dann fällt das generierte P-
- Code-Stück entsprechend kürzer aus:
-
- [ P-Code für die Bedingung ]
- FJP 6
- [ P-Code für Statement1 ]
- L 6
-
-
- Case-Statements:
- Ein Case-Statement wie etwa
-
- Case Expression of
- 2,4 : Statement1-
- 3,6 : Statement2-
- end-
-
- erzeugt den folgenden Code:
-
- [ Code für Expression ]
- UJP 8
- L 10
- [ Code für Statement1 ]
- UJP 9
- Springe aus Case-Statement heraus.
- L 11
- [ Code für Statement2 ]
- UJP 9
- L 8
- CHKI 2 6
-
- Liegt der Wert für Expression im Indexbereich der Sprungtabelle?
-
- LDCI 2
- Lade die untere Grenze auf den Stack.
- SBI
- Subtraktion der unteren Grenze.
- XJP 12
- Indizierter Sprung in die nachfolgende Tabelle:
- L 12
- UJP 10
- UJP 11
- UJP 10
- UJC
- Case error.
- UJP 11
- L 9
-
- Wie man an diesem Beispiel sieht, wird bei jedem Case-Statement
- eine Sprungtabelle, deren Indexbereich vom minimalen und maxima-
- len Case-Label abhängt, angelegt. Wird während der Berechnung von
- "Expression" ein Wert bestimmt, dem der Programmierer kein Case-
- Label zugeordnet hat, so wird der P-Code UJC (Case error) er-
- zeugt. Diese Komponente der Codeerzeugung ist sehr ineffizient,
- denn wählt man als Beispiel folgendes Pascal-Programmstück:
-
- Case i of
- 1 : stat1;
- 1000 : stat2;
- end;
-
- dann wird die Sprungtabelle sehr groß und ist dabei mehr als
- schwach besetzt (998 mal Code für Case error?. Eine andere Mög-
- lichkeit Case-Statements zu implementieren, besteht in der Ver-
- wendung von If-Kaskaden. Unser Beispiel würde intern also umge-
- wandelt zu:
-
- if i = 1 then stat1
- else if i = 1000 then stat2
- else caseerror;
-
- Der Nachteil dieser Implementierung liegt dann aber in der gerin-
- geren Zugriffsgeschwindigkeit auf die Case-Marken mit besonders
- hohen Labelnummern. Der Codeumfang für derartig extreme Beispie-
- le, wie das obige, würde sich jedoch durch die Wahl der letzteren
- Implementierungsmethode entscheidend verringern. Mit der Optimie-
- rung von Case-Statements werden wir uns im übrigen in der ent-
- sprechenden Folge beschäftigen.
-
-
- Repeat-Statements:
- Der Code für ein Repeat-Statement
- repeat
- Statements;
- until Bedingung;
-
- ist
-
- L 10
- [ Code für Statements ]
- [ Code für Bedingung ]
- FJP 10
-
-
- While-Statements:
- Aus einer While-Schleife der Form
-
- while Bedingung do Statement
-
- wird die folgende P-Code Sequenz erzeugt:
-
- L 15
- [ Code für Bedingung ]
- FJP 16
- [ Code für Statement ]
- UJP 15
- L 16
-
- For-Statements:
- Der Code für ein for-Statement
-
- for i := lowerbound to upperbound do statement
-
- ist
-
- [ Code für lowerbound ]
- [ Code, um lowerbound in i zu speichern ]
- [ Code für upperbound ]
- STRI helpvar
- helpvar ist eine lokale, nicht vom Programmierer definierte
- Variable zur Speicherung der oberen Grenze.
- L 8
- [ Code, um den Wert von i auf den Stack zu laden ]
- LODI helpvar
-
- LEQI
- Ist i <= helpvar
- FJP 9
-
- Wenn nein, dann beende forSchleife
-
- [ Code für statement ]
- [ Code, um den Wert von i auf den Stack zu laden ]
- INCI 1
- Inkrementiere i um 1
-
- [ Code, um i zu speichern ]
- UJP 8
- L 9
-
- Wird in einer for-Schleife "downto" anstelle von "to" verwendet,
- so besteht der einzige Unterschied in der Codeerzeugung, daß LEQI
- durch GEQI und INCI durch DECI ersetzt wird.
-
-
- With-Statements:
- Für den Fall, daß innerhalb eines With-Statements eine Record-
- Variable direkt ansprechbar ist, so wird kein extra Code erzeugt.
- Zum Beispiel ist der Code für
-
- with r do f := 0
-
- gleich mit dem Code, der für
-
- r.f := 0
-
- erzeugt wird, nämlich
-
- LDCI 0
- Bringe die Konstante 0 auf den Stack.
- STRI 0 6
- Speichere den Wert unter r.f ab.
-
- Dagegen wird für Variable, auf die indirekt zugegriffen wird,
- unterschiedlicher Code erzeugt. Beispielsweise ergibt die Zuwei-
- sung
-
- p^.f := 0
-
- den P-Code
-
- LODA 0 8
- Bringe die Adresse des Records auf den Stack.
- INCA 1
- Inkrementiere um den Offset der Variablen f.
- LDCI 0
- Bringe die Konstante 0 auf den Stack.
- STOI
- Speichere indirekt.
-
- während
-
- with p^ do f := 0
-
- den Code
-
- LODA 0 8
- Bringe die Adresse des Records auf den Stack.
- STRA 0 9
- Speichere die Adresse in einer lokalen Variablen.
- LODA 0 9
- Greife auf diese Adresse zu
- INCA 1
- Inkrementiere um den Offset der Variablen f.
- LDCI 0
- Bringe die Konstante 0 auf den Stack.
- STOI
- Speichere indirekt.
-
- erzeugt.
-
-
- Ein Beispiel-Programm
-
- Um den Gesamtüberblick nicht zu verlieren und uns mit dem P-Code
- noch etwas vertrauter zu machen, wollen wir zum Schluß noch den
- P-Code für ein komplettes Pascal-Programm betrachten. Als Bei-
- spiel-Programm wählen wir - wie könnte es auch anders sein - das
- jedem Pascal-Programmierer bekannte Fakultäts-Beispiel aus:
-
- PROGRAM fakultaet;
- VAR i : INTEGER;
- FUNCTION fac (n : INTEGER) : INTEGER;
- BEGIN
- IF n = 0 THEN fac := 1
- ELSE fac := n*fac(n-1)
- END;
- BEGIN
- WriteLn('Fakultaetsberechnung.');
- Write('Zahl eingeben : ');
- ReadLn(i);
- WriteLn('Ergebnis : ',fac(i)
- END.
-
- Setzt man unseren Compiler auf dieses Beispiel an, so produziert
- den Code.
-
- So. Mit dem Ende dieses Kapitels hat der Compiler seine Aufgabe
- vollkommen erfüllt. Als Ergebnis der Übersetzung liegt uns nun
- ein P-Code-Programm vor, das nur noch entsprechend interpretiert
- werden muß. Mit der Behandlung des Interpreters wird sich dann
- die nächste Folge beschäftigen.