home *** CD-ROM | disk | FTP | other *** search
/ Turbo Toolbox / Turbo_Toolbox.iso / spezial / 02 / texte / pascomp.5 < prev    next >
Encoding:
Text File  |  1980-01-03  |  22.2 KB  |  650 lines

  1.  
  2. PASCOMP
  3.  
  4. PASCAL baut einen Pascal-Compiler
  5.  
  6. Teil 6: Die Code-Erzeugung
  7.         von Johannes Velmans
  8.  
  9. Mit  der ausführlichen Betrachtung der semantischen Analyse haben
  10. wir  in  der  letzten  Folge  dieser  Serie  die Analysephase des
  11. Compilers  endlich  hinter  uns gebracht. Während dieser gesamten
  12. Analyse   ist   das   Pascal-Quellprogramm   sozusagen  in  seine
  13. "Einzelteile"  zerlegt  worden, wobei alle für ein Pascalprogramm
  14. wichtigen  Komponenten,  wie  zum  Beispiel  Notation, Syntax und
  15. Semantik   eingehend  überprüft  und  auf  ihre  Richtigkeit  hin
  16. untersucht worden sind.
  17.  
  18.  
  19. Nach dieser Analysephase folgt nun im Compiler die Synthesephase,
  20. deren  Aufgabe  darin  besteht, die einzelnen Stückchen wieder zu
  21. einem für die benutzte Maschine verständlichen Programm zusammen-
  22. zusetzen.  Dabei ist von besonderer Wichtigkeit, daß die mit die-
  23. sem Kapitel beginnende Synthese des Maschinenprogramms die Spezi-
  24. fikation  und Aufgabenstellung des Pascal-Quellprogramms in kein-
  25. ster  Weise  verändert.  Hinsichtlich der Codeerzeugung muß nicht
  26. notwendigerweise  ausführbarer  Code  für  eine real existierende
  27. Maschine  erzeugt werden - es kann durchaus Sinn machen, Code für
  28. eine  hardwaremäßig nicht existierende, virtuelle Maschine zu ge-
  29. nerieren.  Dies mag dem einen oder anderen Leser vielleicht nicht
  30. sofort  einleuchten, denkt er doch bei der Codegenerierung in er-
  31. ster Linie an die Geschwindigkeit des ausführbaren Programms.
  32.  
  33. Bei  genauerer Betrachtung erkennt man allerdings, daß die Compi-
  34. lierung  auf  eine  Zwischensprache hin, bis auf den Nachteil der
  35. Geschwindigkeit,  eigentlich  nur  wesentliche Vorteile gegenüber
  36. der maschinenbezogenen Codeerzeugung vorzuweisen hat: Da wäre zum
  37. einem der "kontrollierte Ablauf" des Zwischensprache-Programms zu
  38. nennen, der gegenüber der direkten Codeerzeugung den Vorteil hat,
  39. das Programm während der Ausführungszeit schrittweise ablaufen zu
  40. lassen  und somit die Qualität für etwaige Testprogramme deutlich
  41. zu erhöhen.
  42.  
  43. Solche  Tracing-Möglichkeiten  sind den BASIC-Programmieren unter
  44. den  Lesern  sicherlich schon bestens bekannt. Zum anderen bietet
  45. die Hardware- Unabhängigkeit der Zielsprache den Vorteil, bei der
  46. Übersetzung  des Programms die spezifischen Eigenschaften des be-
  47. nutzten  Prozessors,  deren  Berücksichtigung  wiederum mit einem
  48. Mehraufwand  bei  der  Codeerzeugung  verbunden ist, völlig außer
  49. Acht zu lassen.
  50.  
  51. Diese  Maschinen-Unabhängigkeit  bedingt  somit eine Erhöhung der
  52. Portabilität des eigentlichen Pascal-Programms, was auch im Sinne
  53. des  Erfinders  war. Mit Pascal sollte nämlich keine Programmier-
  54. sprache konstruiert und formuliert werden, die für einen bestimm-
  55. ten  Rechnertyp  spezifisch  ist,  sondern  die neu zu schaffende
  56. Sprache  sollte vielmehr einen "universellen Charakter" besitzen.
  57. Gerade dies ist, wie man heute sagen kann, auch gelungen, denn es
  58. gibt  wohl  kaum  einen  Rechner, für den nicht irgendwann einmal
  59. eine Pascal-Version implementiert wurde.
  60.  
  61. Um  einen  tieferen  Einblick in die Zwischensprache zu gewinnen,
  62. werden  wir uns im nachfolgenden etwas näher auf die theoretische
  63. Maschine, die unseren Zwischencode akzeptiert, konzentrieren.
  64.  
  65.  
  66. Die P-Code Maschine
  67.  
  68. Die  virtuelle  P-Code  Maschine ist vom Prinzip her vergleichbar
  69. mit  einem  konventionellen Computer: Sie besteht in groben Zügen
  70. aus einem Prozessor und einem Speicher. Der hauptsächliche Unter-
  71. schied  zu  einem herkömmlichen Rechner besteht in einem "Mangel"
  72. an  Hardware-Registern.  Es  existieren zwar einige interne Regi-
  73. ster,  die  aber nicht zur freien Verfügung des Benutzers stehen.
  74. Diese  Tatsache  hat eine direkte Auswirkung auf die Art des Pro-
  75. zessor-Befehlssatzes.  Hier  finden wir weniger Befehle, die sich
  76. auf  spezielle  Hardware-Register  beziehen,  sondern eher solche
  77. Befehle,  die eine Auswirkung auf den internen Stack der Maschine
  78. haben, der einen Teil des Speichers darstellt.
  79.  
  80. So  werden zum Beispiel Parameter einer Prozedur bei deren Aufruf
  81. nicht in "schnelle" Register abgelegt, sondern grundsätzlich über
  82. den  Stack  übergeben.  Im  weiteren werden Rückkehradressen oder
  83. Funktionsergebnisse  ebenfalls  nicht in Registern, sondern immer
  84. über  den internen Stack übergeben. Die einzelnen Maschinenbefeh-
  85. le,  die im folgenden zum Begriff des P-Codes zusammengefaßt wer-
  86. den, sind im Speicher untergebracht. Auf sie kann in üblicher Art
  87. und Weise zugegriffen werden. Der Prozessor selbst hat, wie jeder
  88. andere  Prozessor auch, einen definierten Befehlssatz. Die Anzahl
  89. der  Prozessor-Register ist auf fünf beschränkt. Die Register ha-
  90. ben im speziellen die folgende Bezeichnung und Bedeutung:
  91.  
  92. Programmzähler (PC):
  93. Der PC stellt einen Zeiger auf den nächsten auszuführenden Befehl
  94. dar.
  95.  
  96. Stackpointer (SP):
  97. Der SP verweist auf die jeweils aktuelle Spitze des Stacks.
  98.  
  99. Markstackpointer (MP):
  100. Der MP dient zur Kennzeichnung der aktuellen Laufzeitschachtel.
  101.  
  102. Newpointer (NP):
  103. NP  zeigt auf das nächste freie Element im Heap. Der Heap verwal-
  104. tet,  wie bereits in den vorherigen Folgen erläutert, dynamischen
  105. Speicherplatz, der während der Laufzeit des Programms angefordert
  106. und wieder freigegeben werden kann.
  107.  
  108. Extreme-Pointer (EP):
  109. EP  stellt  wie  SP ein Verweis in den Stack dar. Die Stelle, auf
  110. die  er  zeigt,  ist eine maximale Grenze von SP, die nicht über-
  111. schritten wird, wenn die zugehörige Prozedur aufgerufen wird (lo-
  112. kaler  Stack).  Wie  werden nun Stack und Heap organisiert) Sieht
  113. man  den  gesamten Speicher als ein großes eindimensionales Array
  114. an, so legt man einfach fest, daß der Stack an der unteren Grenze
  115. und  der  Heap  an  der  oberen Grenze dieses Arrays beginnt. Der
  116. Stack  wächst  also  mit steigenden Adressen, der Heap wächst mit
  117. fallenden  Adressen.  Verwaltet wird der Heap durch die Standard-
  118. Funktionen  New  (neuer  Speicherplatz wird angefordert) und Mark
  119. und Release (benutzter Speicherplatz wird wieder freigegeben).
  120.  
  121. Der  Stack  besitzt jedoch noch eine weitere interne Struktur. Er
  122. dient  dort  in erster Linie zur Aufnahme von sogenannten "Stack-
  123. frames"  (Laufzeitschachteln).  Zur  Erinnerung:  Eine  Laufzeit-
  124. schachtel wird erzeugt und auf dem Stack untergebracht, wenn eine
  125. benutzerdefinierte  Funktion oder Prozedur aktiviert wird. Um die
  126. Codeerzeugung  zu vereinfachen, wurde für Prozeduren und Funktio-
  127. nen  der gleiche Laufzeitschachtel-Aufbau gewählt. Eine besondere
  128. Erwähnung  findet  an  dieser  Stelle  die  Laufzeitschachtel des
  129. Hauptprogramms,  deren Aktivierung nicht explizit geschieht, son-
  130. dern mit dem Start des Programms stattfindet.
  131.  
  132. Der  Markstackpointer  (MP)  verweist auf den Anfang der obersten
  133. auf  dem  Stack liegenden Laufzeitschachtel. MP hat zwei Funktio-
  134. nen:  Zum  einen  reorganisiert er den Stack, wenn die Ausführung
  135. einer Prozedur oder Funktion beendet wird. Zum anderen liefert er
  136. die Basisadresse, mit deren Hilfe die Adressen von lokalen Varia-
  137. blen gewonnen werden können. Der Extreme-Stackpointer EP verweist
  138. dagegen  auf  die  Spitze  der aktuellen Laufzeitschachtel. Durch
  139. einen  Vergleich  von  EP und MP ist es während der Laufzeit mög-
  140. lich,  festzustellen,  ob  der Stack in den Heap oder der Heap in
  141. den  Stack  überläuft.  Durch diese Konstruktion wird eine Stack-
  142. pointer-Überprüfung bei der sehr häufig auftretenden Inkrementie-
  143. rung  des  Stackpointers überflüssig, was wiederum eine Geschwin-
  144. digkeitsverbesserung während der Laufzeit zur Folge hat.
  145.  
  146. Da  die maximale Größe eines Stackframes (Laufzeitschachtel) wäh-
  147. rend  der  Übersetzungszeit bereits bekannt ist, geht ihr Wert in
  148. EP  mit  ein.  Der lokale Stack wird benötigt, um temporäre Werte
  149. während  der Ausführung einer Prozedur oder Funktion aufzunehmen.
  150. Temporäre Werte können Werte von lokalen Variablen sein oder Wer-
  151. te  von Hilfsvariablen, die für den internen Ablauf von Kontroll-
  152. strukturen (for-, while-Schleifen) benötigt werden.
  153.  
  154. Zur  näheren  Erläuterung  sei die Auswirkung eines speziellen P-
  155. Code-Befehls  auf  den  Stack betrachtet. Als Beispiel wählen wir
  156. den  sbi-Befehl.  sbi nimmt zwei Werte vom Stack und legt das Er-
  157. gebnis  der  Subtraktion  wieder auf den Stack zurück. Der Stack-
  158. pointer wird bei dieser Operation um den Wert 1 dekrementiert.
  159.  
  160. Für das weitere Verständnis des P-Codes ist es erforderlich, sich
  161. einen  Einblick in die Organisation der Laufzeitschachtel zu ver-
  162. schaffen.  Mit Organisation ist hier der Vorgang gemeint, in wel-
  163. cher  Weise bei einem Aufruf einer Prozedur die Laufzeitschachtel
  164. auf  dem  Stack  aufgebaut  wird. Als Beispiel soll hier ein Aus-
  165. schnitt aus einem Pascal-Programm dienen:
  166.  
  167.  ...
  168.   PROCEDURE  a;
  169.    PROCEDURE  b;
  170.     PROCEDURE  c;
  171.      BEGIN
  172.      ...  (*  statischer  Vorgänger:  b  *)
  173.      END;
  174.     PROCEDURE  d;
  175.      BEGIN ...
  176.      (* statischer Vorgänger: b *)
  177.      END;
  178.     BEGIN (*  b *)
  179.      ...  * statischer Vorgänger: a *)
  180.     END;
  181.    Begin (* a *)
  182.    ...
  183.    END;
  184.  ...
  185.  
  186. Mit  Hilfe  des statischen Vorgängers ist es möglich, während der
  187. Übersetzung die Anfangsadressen von lokalen Variablen in textuell
  188. vorher definierten Blöcken zu gewinnen. Das bedeutet, daß es über
  189. den  statischen Vorgänger möglich ist, alle gültigen Definitionen
  190. von  Variablen  in  einem bestimmten und - bezogen auf die gerade
  191. betrachtete Prozedur - gültigen Block herauszufinden.
  192.  
  193. Der  dynamische  Vorgänger  verweist  auf  die  Laufzeitschachtel
  194. derjenigen  Prozedur,  die  die gerade aktive Prozedur aufgerufen
  195. hat.  Über  das  Zusammenspiel  von  statischen  und  dynamischen
  196. Vorgängern  soll  das  nächste, rekursive Beispiel einen Einblick
  197. vermitteln. Dazu sei das folgende Pascal-Programm gegeben:
  198.  
  199. PROGRAM hauptprogramm;
  200.  VAR j : INTEGER;
  201.  PROCEDURE p;
  202.   VAR i : INTEGER;
  203.   PROCEDURE q;
  204.    BEGIN
  205.     IF i > 0 THEN BEGIN
  206.      i := Pred(i);
  207.      j := Succ(j);
  208.      q; (* rekursiver Aufruf von q *)
  209.     END
  210.   ELSE Write(0)
  211.   END; (* q *)
  212.  BEGIN (* p *)
  213.   i := 2;
  214.   q;
  215.  END; (* p *)
  216.  
  217.  
  218.  
  219. BEGIN (* hauptprogramm *)
  220.  j := 0;
  221.  p;
  222. END.
  223.  
  224.  
  225. Dieses Progrämmchen bewirkt eine Laufzeitkeller-Konfiguration.
  226.  
  227.  
  228. Aufbau einer Laufzeitschachtel
  229.  
  230. Es  gibt drei P-Code Befehle, die eine Laufzeitschachtel komplett
  231. aufbauen. Dieses sind die Befehle mst, cup und ent. Die Bedeutung
  232. der Befehle im einzelnen:
  233.  
  234. mst:
  235. /Mark  Stack).  Der Mark-Stack-Befehl markiert die aktuelle Posi-
  236. tion  im  Stack,  die  dann später den Anfang der neuen Laufzeit-
  237. schachtel  darstellen  wird. Als Parameter wird mst eine Integer-
  238. Zahl  k  übergeben, die ein Indikator für die Blockschachtelungs-
  239. tiefe der Prozedur ist. k berechnet sich dabei folgendermaßen:
  240.  
  241. k :=  1 +
  242.           (Blockschachtelungstiefe
  243.            der aufrufenden Prozedur)
  244.  
  245.           (Blockschachtelungstiefe
  246.            der aufgerufenen Prozedur)
  247.  
  248. Die  Ausführung  von  mst im Interpreter löst eine Berechnung der
  249. Adressen  für  den  statischen  und dynamischen Vorgänger aus und
  250. rettet  den  Extreme-Stackpointer EP für spätere Zwecke. Sind der
  251. statische  und  dynamische Vorgänger bestimmt, so wird der Stack-
  252. pointer  SP  soweit inkrementiert, bis er auf den Bereich für die
  253. Parameter  verweist.  An  dieser Stelle trifft man dann in der P-
  254. Code-Sequenz  auf eine Code-StÜck, das die Pr ozedurparameter auf
  255. den  Stack  lädt.  Die  typische Sequenz einer Laufzeitschachtel-
  256. Konstruktion läßt sich dann wie folgt darstellen:
  257.  
  258. mst 0
  259. [Code, um Parameter auf den Stack zu laden]
  260. cup 0 3
  261.  
  262. cup:
  263. "Call  User Procedure". Dieser Befehl bewirkt, daß MP auf den Be-
  264. ginn  der neuen Laufzeitschachtel gesetzt und die Rückkehradresse
  265. bestimmt wird. Schließlich ruft cup einen Sprung zu der durch den
  266. zweiten  Parameter angegebenen Marke hervor. Durch den ersten Pa-
  267. rameter  wird  an die aufzurufende Prozedur eine Information über
  268. die Größe der Parameter in "Speicherplätze" gegeben. Ist der cup-
  269. Befehl  beendet, so verweist der Programmzähler PC auf den Anfang
  270. der auszuführenden Prozedur.
  271.  
  272.  
  273. ent:
  274. "Enter  User  Defined Procedure". Diesen Befehl findet man gleich
  275. zweimal  zu  Beginn einer jeden benutzerdefinierten Prozedur. Die
  276. Operanden  dieses  Befehls  definieren die Größe des Laufzeitkel-
  277. lers.  ent  besitzt  zwei  Parameter. Hat der erste Parameter den
  278. Wert  eins,  dann gibt der zweite Parameter den Speicherplatz an,
  279. der  für  lokale Variable benötigt wird. Der Stackpointer SP wird
  280. demzufolge  entsprechend  inkrementiert.  Ist der Wert des ersten
  281. Parameters  zwei  ,  so  gibt der Wert des zweiten Parameters die
  282. Größe  des  lokalen  Stacks  an und EP, der Extreme-Stackpointer,
  283. wird aktualisiert. An dieser Stelle wird dann während der Ausfüh-
  284. rungszeit  durch  den Interpreter eine Laufzeitüberprüfung veran-
  285. laßt.  Diese  Überprüfung  entscheidet,  ob der Stack in den Heap
  286. überläuft oder nicht.
  287.  
  288. Die  Laufzeitschachtel für das Hauptprogramm wird in der gleichen
  289. Weise  gehandhabt - mit der Ausnahme, daß vier Speicherplätze für
  290. Filezugriffe   auf   die  Standardfiles  Input  und  Output  fest
  291. reserviert   sind.  Diese  vier  Speicherplätze  sind  wie  folgt
  292. organisiert:
  293. Jeweils zwei Speicherplätze gehören zu einer Textdatei. Davon ist
  294. der  erste  Speicherplatz der Filepuffer und der zweite Speicher-
  295. platz  das  Filehandle. Das Filehandle kennzeichnet die Datei in-
  296. tern im Interpreter, wozu ihr eine Nummer zur Verwaltung zugeord-
  297. net  wird.  Für  das Standardfile Input ist das Filehandle als -4
  298. definiert. Die Datei Output erhält das Filehandle -3 als Konstan-
  299. te  zugewiesen.  Der Wertebereich des Filehandles beschränkt sich
  300. auf  den  Bereich von -4 bis 4 (ohne 0), so daß eine maximale An-
  301. zahl  von  acht  offenen Dateien nicht überschritten werden darf.
  302. Ein  Wert  kleiner  als  Null kennzeichnet ein Textfile, ein Wert
  303. größer Null kennzeichnet ein Nicht-Textfile.
  304.  
  305.  
  306. Codeerzeugung für Statements
  307.  
  308. Es  existieren  in  der Pascal-Syntax neun verschiedene Arten von
  309. Statements: if-, else-, while-, repeat-, for-, with-, goto-, com-
  310. pound-, assignment- und procedure-call-Statements. Auf welche Art
  311. und  Weise  nun  für  die einzelnen Statements Code erzeugt wird,
  312. soll hier kurz dargestellt werden:
  313.  
  314. Assignments:
  315. Für Zuweisungen benutzt man drei verschiedene Code-Stücke, die je
  316. nach  der  Beschaffenheit der Zuweisung verwendet werden. Im ein-
  317. fachsten  Fall wird direkt ein Wert an eine Variable eines einfa-
  318. chen Typs, also kein Array oder Record, zugewiesen:
  319.  a :=0
  320.  
  321. a  soll  hierbei eine Variable sein. Der erzeugte P-Code ist dann
  322. folgendermaßen organisiert:
  323.  
  324. LDCI 0
  325. bringe die Konstante 0 auf den Stack.
  326. STRI 0 5
  327. Speichere  das  oberste  Element  des  Stacks  in  der  aktuellen
  328. Schachtel  (Blockschachtelungstiefe  =  0)  bei  Relativadresse 5
  329. (Adresse von a) ab.
  330.  
  331. Ist  a  aber  eine globale, also eine im Hauptprogramm definierte
  332. Variable, so wird das folgende P-Code-Stück erzeugt:
  333.  
  334. LDCI 0
  335. Bringe die Konstante 0 auf den Stack.
  336. STOI 10
  337. Speichere  das oberste Element des Stacks in a ab. Die Variable a
  338. ist  dabei  in der Hauptprogramm-Schachtel mit der Relativadresse
  339. 10 definiert.
  340.  
  341. Der  nächste  mögliche  Fall einer Zuweisung tritt dann ein, wenn
  342. die  Variable  von  einem  einfachen  Typ ist, die Zuweisung aber
  343. indirekt  über  einen  Zeiger  oder  über  eine  formale Variable
  344. erfolgt. Ein Beispiel hierfür wäre etwa:
  345. p^  :=  0 oder f := 0 wobei p und f lokal definiert sind. Die vom
  346. Compiler erzeugte P-Code-Sequenz sieht dann so aus:
  347.  
  348. LODA 0 5
  349. Bringe die Adresse der Variablen auf den Stack.
  350. LDCI 0
  351. Bringe die Konstante 0 auf den Stack.
  352. STRI
  353. Speichere  die  Konstante indirekt an der auf dem Stack liegenden
  354. Adresse ab.
  355.  
  356. Ist  p  global  (f  kann als formale Variable nicht global sein),
  357. dann ändert sich nur die Bestimmung der Variablenadresse:
  358.  
  359. LDOA
  360. Die  Variable ist im Hauptprogramm mit der Relativadresse 9 defi-
  361. niert. Bringe die Absolutadresse der Variablen auf den Stack.
  362. LDCI 0
  363. Bringe die Konstante 0 auf den Stack.
  364. STRI
  365. Speichere  die  Konstante indirekt an der auf dem Stack liegenden
  366. Adresse ab.
  367.  
  368. Schließlich  gibt es als dritten Zuweisungsfall noch die Möglich-
  369. keit, Variablen von zusammengesetzten Typen aneinander zuzuweisen
  370. (Arrays oder Records). Auch hier wiederum ein Beispiel:
  371.  
  372. r := s
  373.  
  374. r und s sollen auch hier lokal definiert sein. Da hier nun belie-
  375. bige  Typen  zugelassen  sind, muß die Komponente des Speicherum-
  376. fangs  der  benutzten Variablen in die P-Codeerzeugung mit einge-
  377. hen:
  378.  
  379. LDA 0 5
  380. Lade die Adresse von r auf den Stack.
  381. LDA 0 99
  382. Lade die Adresse von s auf den Stack.
  383. MOV 20
  384. Kopiere 20 Speicherplätze von s nach r.
  385.  
  386. Sind  r und s keine lokalen, sondern global definierte Variablen,
  387. so wird in der Codeerzeugung der Befehl LAO anstelle von LDA ver-
  388. wendet.
  389.  
  390. Goto-Statements:
  391. Der  P-Code für Goto-Statements besteht nur aus einem unbedingten
  392. Sprung zu einem angegebenen Label:
  393.  
  394. UJP 5
  395. Springe ohne Bedingung zu Label 5.
  396.  
  397. Compound-Statements:
  398. Compound-Statements  erzeugen  keinen eigenständigen P-Code, son-
  399. dern  "verwalten" nur eine Liste von Statements, was man der Pas-
  400. cal-Syntax leicht entnehmen kann.
  401.  
  402. If-Statements:
  403. Der Code für ein if-Statement
  404.  
  405. If Bedingung then Statement1
  406. else Statement2
  407.  
  408. wird folgendermaßen erzeugt:
  409.  
  410. [ P-Code für die Bedingung ]
  411. FJP 6
  412. [ P-Code für Statement1 ]
  413. UJP 7
  414. L 6
  415. [ P-Code für Statement2 ]
  416. L 7
  417.  
  418. Fehlt der else-Teil im if-Statement, dann fällt das generierte P-
  419. Code-Stück entsprechend kürzer aus:
  420.  
  421. [ P-Code für die Bedingung ]
  422. FJP 6
  423. [ P-Code für Statement1 ]
  424. L 6
  425.  
  426.  
  427. Case-Statements:
  428. Ein Case-Statement wie etwa
  429.  
  430. Case Expression of
  431.  2,4 : Statement1-
  432.  3,6 : Statement2-
  433. end-
  434.  
  435. erzeugt den folgenden Code:
  436.  
  437. [ Code für Expression ]
  438. UJP 8
  439. L 10
  440. [ Code für Statement1 ]
  441. UJP 9
  442. Springe aus Case-Statement heraus.
  443. L 11
  444. [ Code für Statement2 ]
  445. UJP 9
  446. L 8
  447. CHKI 2 6
  448.  
  449. Liegt der Wert für Expression im Indexbereich der Sprungtabelle?
  450.  
  451. LDCI 2
  452. Lade die untere Grenze auf den Stack.
  453. SBI
  454. Subtraktion der unteren Grenze.
  455. XJP 12
  456. Indizierter Sprung in die nachfolgende Tabelle:
  457. L 12
  458. UJP 10
  459. UJP 11
  460. UJP 10
  461. UJC
  462. Case error.
  463. UJP 11
  464. L 9
  465.  
  466. Wie  man  an diesem Beispiel sieht, wird bei jedem Case-Statement
  467. eine  Sprungtabelle, deren Indexbereich vom minimalen und maxima-
  468. len Case-Label abhängt, angelegt. Wird während der Berechnung von
  469. "Expression"  ein Wert bestimmt, dem der Programmierer kein Case-
  470. Label  zugeordnet  hat,  so  wird der P-Code UJC (Case error) er-
  471. zeugt.  Diese  Komponente der Codeerzeugung ist sehr ineffizient,
  472. denn wählt man als Beispiel folgendes Pascal-Programmstück:
  473.  
  474. Case i of
  475.    1 : stat1;
  476.  1000 : stat2;
  477. end;
  478.  
  479. dann  wird  die  Sprungtabelle  sehr  groß und ist dabei mehr als
  480. schwach  besetzt  (998 mal Code für Case error?. Eine andere Mög-
  481. lichkeit  Case-Statements  zu implementieren, besteht in der Ver-
  482. wendung  von  If-Kaskaden. Unser Beispiel würde intern also umge-
  483. wandelt zu:
  484.  
  485. if i = 1 then stat1
  486. else if i = 1000 then stat2
  487. else caseerror;
  488.  
  489. Der Nachteil dieser Implementierung liegt dann aber in der gerin-
  490. geren  Zugriffsgeschwindigkeit  auf die Case-Marken mit besonders
  491. hohen  Labelnummern. Der Codeumfang für derartig extreme Beispie-
  492. le, wie das obige, würde sich jedoch durch die Wahl der letzteren
  493. Implementierungsmethode entscheidend verringern. Mit der Optimie-
  494. rung  von  Case-Statements  werden wir uns im übrigen in der ent-
  495. sprechenden Folge beschäftigen.
  496.  
  497.  
  498. Repeat-Statements:
  499. Der Code für ein Repeat-Statement
  500. repeat
  501.  Statements;
  502. until Bedingung;
  503.  
  504. ist
  505.  
  506. L 10
  507. [ Code für Statements ]
  508. [ Code für Bedingung ]
  509. FJP 10
  510.  
  511.  
  512. While-Statements:
  513. Aus einer While-Schleife der Form
  514.  
  515. while Bedingung do Statement
  516.  
  517. wird die folgende P-Code Sequenz erzeugt:
  518.  
  519. L 15
  520. [ Code für Bedingung ]
  521. FJP 16
  522. [ Code für Statement ]
  523. UJP 15
  524. L 16
  525.  
  526. For-Statements:
  527. Der Code für ein for-Statement
  528.  
  529. for i := lowerbound to upperbound do statement
  530.  
  531. ist
  532.  
  533. [ Code für lowerbound ]
  534. [ Code, um lowerbound in i zu speichern ]
  535. [ Code für upperbound ]
  536. STRI helpvar
  537. helpvar  ist  eine  lokale,  nicht  vom  Programmierer definierte
  538. Variable zur Speicherung der oberen Grenze.
  539. L 8
  540. [ Code, um den Wert von i auf den Stack zu laden ]
  541. LODI helpvar
  542.  
  543. LEQI
  544. Ist i <= helpvar
  545. FJP 9
  546.  
  547. Wenn nein, dann beende forSchleife
  548.  
  549. [ Code für statement ]
  550. [ Code, um den Wert von i auf den Stack zu laden ]
  551. INCI 1
  552. Inkrementiere i um 1
  553.  
  554. [ Code, um i zu speichern ]
  555. UJP 8
  556. L 9
  557.  
  558. Wird  in einer for-Schleife "downto" anstelle von "to" verwendet,
  559. so besteht der einzige Unterschied in der Codeerzeugung, daß LEQI
  560. durch GEQI und INCI durch DECI ersetzt wird.
  561.  
  562.  
  563. With-Statements:
  564. Für  den  Fall,  daß innerhalb eines With-Statements eine Record-
  565. Variable direkt ansprechbar ist, so wird kein extra Code erzeugt.
  566. Zum Beispiel ist der Code für
  567.  
  568. with r do f := 0
  569.  
  570. gleich mit dem Code, der für
  571.  
  572. r.f := 0
  573.  
  574. erzeugt wird, nämlich
  575.  
  576. LDCI 0
  577. Bringe die Konstante 0 auf den Stack.
  578. STRI 0 6
  579. Speichere den Wert unter r.f ab.
  580.  
  581. Dagegen  wird  für  Variable,  auf die indirekt zugegriffen wird,
  582. unterschiedlicher  Code erzeugt. Beispielsweise ergibt die Zuwei-
  583. sung
  584.  
  585. p^.f := 0
  586.  
  587. den P-Code
  588.  
  589. LODA 0 8
  590. Bringe die Adresse des Records auf den Stack.
  591. INCA 1
  592. Inkrementiere um den Offset der Variablen f.
  593. LDCI 0
  594. Bringe die Konstante 0 auf den Stack.
  595. STOI
  596. Speichere indirekt.
  597.  
  598. während
  599.  
  600. with p^ do f := 0
  601.  
  602. den Code
  603.  
  604. LODA 0 8
  605. Bringe die Adresse des Records auf den Stack.
  606. STRA 0 9
  607. Speichere die Adresse in einer lokalen Variablen.
  608. LODA 0 9
  609. Greife auf diese Adresse zu
  610. INCA 1
  611. Inkrementiere um den Offset der Variablen f.
  612. LDCI 0
  613. Bringe die Konstante 0 auf den Stack.
  614. STOI
  615. Speichere indirekt.
  616.  
  617. erzeugt.
  618.  
  619.  
  620. Ein Beispiel-Programm
  621.  
  622. Um  den Gesamtüberblick nicht zu verlieren und uns mit dem P-Code
  623. noch  etwas  vertrauter zu machen, wollen wir zum Schluß noch den
  624. P-Code  für  ein  komplettes Pascal-Programm betrachten. Als Bei-
  625. spiel-Programm  wählen wir - wie könnte es auch anders sein - das
  626. jedem Pascal-Programmierer bekannte Fakultäts-Beispiel aus:
  627.  
  628. PROGRAM fakultaet;
  629. VAR i : INTEGER;
  630. FUNCTION fac (n : INTEGER) : INTEGER;
  631.  BEGIN
  632.   IF n = 0 THEN fac := 1
  633.   ELSE fac := n*fac(n-1)
  634.  END;
  635. BEGIN
  636.  WriteLn('Fakultaetsberechnung.');
  637.  Write('Zahl eingeben : ');
  638.  ReadLn(i);
  639.  WriteLn('Ergebnis : ',fac(i)
  640. END.
  641.  
  642. Setzt  man unseren Compiler auf dieses Beispiel an, so produziert
  643. den Code.
  644.  
  645. So.  Mit  dem Ende dieses Kapitels hat der Compiler seine Aufgabe
  646. vollkommen  erfüllt.  Als  Ergebnis der Übersetzung liegt uns nun
  647. ein  P-Code-Programm vor, das nur noch entsprechend interpretiert
  648. werden  muß.  Mit  der Behandlung des Interpreters wird sich dann
  649. die nächste Folge beschäftigen.
  650.