Universität Siegen Fachbereich 12, Angewandte Informatik und Elektrotechnik Dozent: Dr. Kurt Sieber; Compilerbau II Skript Christian Uhrhan 18. März 2011
Inhaltsverzeichnis 1 Syntaxgerichtete Übersetzung 1 1.1 Semantische Regeln............................... 3 1.2 Auswertungsreihenfolge............................ 4 1.3 Topologische Sortierung............................ 4 1.4 Auswertungsreihenfolge für S-Attributierte Grammatiken......... 6 2 Attrebutauswertung bei L-Attributierten Grammatiken 7 3 Syntaxgerichtete Übersetzungsschemata (ÜS) 10 3.1 Semantik eines SDT.............................. 10 3.2 Übersetzung von S- bzw. L-attributierten Grammatiken in SDT s..... 10 4 Generierung von Zwischencode 11 4.1 Übersetzung arithmetischer Ausdrücke in 3-Adress-Code.......... 13 4.2 Übersetzung boolscher Ausdrücke....................... 13 4.3 Jumpcode für boolsche Ausdrücke...................... 14 4.4 Backpatching.................................. 17 4.4.1 Boolsche Ausdrücke.......................... 17 4.4.2 Anweisungen.............................. 17 5 Laufzeitumgebungen 17 5.1 Speicherorganisation.............................. 17 5.2 Aufteilung zwischen Stack und Heap..................... 17 5.3 Zugriff auf globale Daten........................... 17 5.4 Bestimmung des Access Links......................... 17 5.5 Prozeduren auf Parameterposition...................... 17 5.6 Alternative zu Access Links: Displays.................... 17 5.7 calling sequences und return sequences.................... 17 5.7.1 Design-Prinzipien............................ 17 5.7.2 Aufteilung der calling sequence.................... 17 5.7.3 Codeerzeugung für Programme mit Prozeduren (Pascal-artige Sprache)................................... 17 5.7.4 Vorgehensweise bei der Übersetzung................. 17 5.7.5 Übersetzung einer Prozedurdeklaration............... 17 5.7.6 Übersetzung eines Prozeduraufrufs.................. 17 6 Codegenerierung (Drachenbuch Kpt. 8) 17 6.1 Befehlssatz der Zielmaschine (RISC-Architektur).............. 17 6.2 Basisblöcke und Flussgraphen......................... 17 6.3 Optimierung der Basisblöcke......................... 17 6.4 Vom (optimierten) DAG zurück zum Zwischencode............. 17 6.5 Ein einfacher Codegenerator.......................... 17 6.6 Globale Register Vergabe........................... 17 i
7 Instruction Selection (Drachenbuch Kpt. 8.9) 17 7.1 Generierung von optimalem Code für Ausdrücke.............. 17 7.1.1 Algorithmus zur Berechnung der Ershov-Zahl............ 17 ii
1 Syntaxgerichtete Übersetzung Bisherige Sicht: Aus einer KFG G wird ein Parser erzeugt, also ein deterministischer Kellerautomat, der die Sprache L(G) akzeptiert. D.h.: Der Parser antwortet nur mit Ja oder Nein je nachdem, ob das Eingabewort w L(G) oder w / L(G). In der Praxis: Der Parser sollte eine Antwort zurückliefern, z. B.: (a) einen abstrakten Syntaxbaum für das Eingabewort w (mit dem man anschließend weiterarbeitet, z. B. Typüberprüfung in der semantischen Analyse oder Generierung von Zwischencode). (b) oder schon eine Übersetzung des Eingabewortes w in Zwischencode (c) oder in einfachen Fällen (z. B. wenn w Eingabe für einen Taschenrechner) schon ein Ergebnis. Deshalb als Eingabe für Parser-Generatoren (JavaCup, yacc, SML-Yacc, O Caml- Yacc) nicht nur eine KFG, sondern man ergänzt die Produktionen der KFG durch sogenannte semantische Regeln oder semantische Aktionen. Beispiel: 1. Yacc-Eingabe: Spezifikation eines Taschenrechners (siehe AB01). expr : exp + term {$$ = $1 + $3; } Zugehörige Theorie: Attributierte Grammatiken (Verallgemeinerung von AG: syntaxgerichtete Definitionen (engl. SDD) Verwandter Mechanismus: syntaxgerichtete Übersetzungen (engl. SDT)) Beispiel: 2. für attributierte Grammatiken: Arithmetische Ausdrücke für Taschenrechner Nonterminals: L, E, T, F Terminalzeichen: n, +,, (, ), digit Jedem Nonterminal wird ein einziges Attribut zugeordnet, nämlich val (= value). Dem Terminalzeichen digit ist ein Attribut lexval zugeordnet (wird vom Scanner geliefert). Die Produktionen und sematische Regeln lauten: 1
Produktion semantische Regel (1) L En L.val = E.val (2) E E 1 + T E.val = E 1.val + T.val (3) E T E.val = T.val (4) T T 1 F T.val = T 1.val F.val (5) T F T.val = F.val (6) F (E) F.val = E.val (7) F digit F.val = digit.lexval Mit den semantischen Regeln werden die Attributwerte für jeden Syntaxbaum berechnet, z. B. attributierter Syntaxbaum für 3 5 + 4n L val = 19 E val = 19 n E val = 15 + T val = 4 T val = 15 F val = 4 T val = 3 F val = 5 digit lexval = 4 F val = 3 digit lexval = 5 digit lexval = 3 Definition 1 (Attributierte Grammatik). Sei G = (N, T, P, S) eine KFG, V = N T Eine attributierte Grammatik über G besteht aus einer endlichen Menge A von Attributen (oder Attributnamen) einem Wertebereich D a a A zwei Abbildungen: syn : V P(A) inh : V P(A) mit inh(s) = inh(x) = x T 2
Sprechweise: a syn(x) heißt synthetisches Attribut von X a inh(x) heißt geerbtes Attribut von X Es gilt: Attr(X) = syn(x) inh(x) einer Menge sem(p) von semantischen Regeln für jede Produktion p P. 1.1 Semantische Regeln Wie sehen die semantischen Regeln aus? Sei p eine Produktion der Form X 0 X 1... X m (wobei aus jedem X i die Position i ablesbar ist). Dann ist jede semantische Regel für p von der Form: mit X i0.a 0 = f(x i1.a 1,..., X ik.a k ) i 0,..., i k {0,..., m} (d.h. i 0,..., i k sind Positionen in p) a j Attr(X ij ) (d.h. a j ist ein zu X ij f : D a1 D ak D a0 passendes Attribut) (d.h. f hat passenden Typ ) X i0.a 0 kommt nicht in den Argumenten von f vor Weiter muss gelten: Für jedes a syn(x 0 ) enthält sem(p) genau eine Regel mit linker Seite X 0.a Für jedes a inh(x i ) mit i {1,..., m} enthält sem(p) genau eine Regel mit linker Seite X i.a. Im ersten Fall dürfen als Attribute von f nur Attribute von X 1,..., X m oder geerbte Attribute von X 0 auftreten. Weitere Regeln dürfen in sem(p) nicht vorkommen. Beispiel: 3. Aus vorhergehendem Beispiel val ist synthetisches Attribut für die Nonterminals L, E, T, F lexval ist synthetisches Attribut für digit Keine geerbten Attribute Weiteres Beispiel: Nicht linksrekursive KFG für arithmetische Ausdrücke. Hier: nur ein Teil davon: Attributierter Syntaxbaum für 3 5: T val = 15 F val = 3 inh = 3 T syn = 15 digit lexval = 3 F val = 5 inh = 15 T syn = 15 digit lexval=5 ɛ 3
(1) T F T T.inh = T.val T.val = T.syn (2) T F T 1 T 1.inh = T.inh F.val T.syn = T 1.syn (3) T ɛ T.syn = T.inh (4) F digit F.val = digit.lexval 1.2 Auswertungsreihenfolge Gibt es überhaupt eine Reihenfolge, in der die Attribute ausgewertet werden? Wenn ja, welche? Dazu: Betrachte den sogenannten Abhängigkeitsgraphen zu einem attributierten Syntaxbaum (dependency graph). Der Abhängigkeitsgraph ist ein gerichteter Graph und ist wie folgt definiert: (a) Die Knoten des Abhängigkeitsgraphen sind alle Attributvorkommen im attributierten Syntaxbaum, d.h. für jeden mit X markierten Knoten des Syntaxbaums und jedes a Attr(X) gibt es einen Knoten X.a im Abhängigkeitsgraphen. (b) Die Kanten des Abhängigkeitsgraphen geben die Abhängigkeit zwischen den Attributvorkommen wieder, d.h. für jede semantische Regel X.a = f(..., Y.b,... ) gibt es eine Kante vom passenden Y.b zum passenden X.a. Beachte: Die Kante für ein synthetisches Attribut X.a verläuft vom Kind- zum Vaterknoten oder von X.b nach X.a, wobei b geerbtes Attribut. Die Kante für ein geerbtes Attribut X.a verläuft vom Vater- zum Kindknoten oder zwischen Geschwisterknoten. Beispiel: 4. Nicht linksrekursive Grammatik für arithmetische Ausdrücke, Syntaxbaum für 3 5 z. B. wegen T 1.inh = T.inh F.val gibt es Kanten von 5 nach 6 und von 4 nach 6. Am Abhängigkeitsgraphen kann man erkennen, ob es eine Auswertungsreihenfolge gibt (und kann sie dann auch finden). 1.3 Topologische Sortierung Definition 2. Eine topologische Sortierung eines gerichteten Graphen ist eine lineare Anordnung N 1,..., N k aller Knoten des Graphen, so dass gilt: Wenn eine Kante von N i nach N j verläuft, dann ist i < j. 4
Beispiel: 5. (1),...,(9) ist eine mögliche topologische Sortierung im Beispiel. Eine andere: (1),(3),(5),(2),(4),(6),...,(9) Bemerkung 1. Eine topologische Sortierung existiert genau dann, wenn der Graph keine Zyklen besitzt. ist klar Wenn kein Zykel existiert, dann existiert ein Knoten ohne Vorgänger. Diesen nimmt man als ersten der Sortierung. Dann induktiv weiter mit Restgraph. Problem: Topologische Sortierung ist zwar effizient möglich. Aber: Man will es nicht für jeden Syntaxbaum wieder neu durchführen. Frage: Kann man für eine attributierte Grammatik an Hand der semantischen Regeln eine Auswertungsstrategie für alle Syntaxbäume festlegen (oder wenigstens überprüfen, ob alle Abhängigkeitsgraphen zu attributierten Syntaxbaum zyklenfrei sind)? Das Problem ist entscheidbar, aber nur in exponentialer Laufzeit (in Abhängigkeit von der Größe der attributierten Grammatik) nicht in der Praxis durchführbar. Deshalb: Man schränkt sich auf spezielle einfache attributierte Grammatiken ein, die eine einfache Auswertungsstrategie ermöglichen. Definition 3 (S- und L-Attributierte Grammatik). 1. Eine attributierte Grammatik heißt S-Attributiert, wenn sie nur synthetische Attribute hat. 2. Eine attributierte Grammatik heißt L-Attributiert, wenn für alle geerbten Attribute folgendes gilt: Wenn X i.a geerbtes Attribut zur Produktion A X 1... X n, dann darf X i.a nur abhängen von Beispiel: 6. a) geerbten Attributen von A b) beliebigen Attributen von X 1,..., X i 1 (die links von X i stehen) c) beliebigen Attributen von X i selbst, wobei unter den Attributen von X i keine Zyklen entstehen dürfen. 1. linksrekursive attributierte Grammatik für arithmetische Ausdrücke (mit einzigem Attribut val ) ist S-Attributiert 5
2. nicht linksrekursive attributierte Grammatik für arithmetische Ausdrücke (mit val, syn, inh) ist L-Attributiert, denn die sematischen Regeln für das einzige geerbte Attribut inh lauten: T F T T.inh = F.val ist korrekt (da F links von T ) T F T 1 T 1.inh = T.inh F.val ist korrekt (da T Kopf der Prod. und F links von T 1 ) 1.4 Auswertungsreihenfolge für S-Attributierte Grammatiken Da es nur synthetische Attribute gibt, ist jeder Attributwert nur von den Attributwerten der Söhne abhängig (alle Kanten des Abhängigkeitsgraphen verlaufen von unten nach oben). Der Abhängigkeitsgraph ist sogar ein Baum. Jede bottom up -Auswertung ist möglich, insbesondere die postorder-reihenfolge. postorder ist dadurch charakterisiert, dass jeder Knoten hinter seinen Kindern und hinter seinen linken Geschwistern liegt. Auswertung in postorder-reihenfolge lässt sich rekursiv definieren: Algorithmus 1. postorder-eval(n) führe postorder-eval(c) für alle Kinder C von N in der Reihenfolge von links nach rechts durch, berechne alle Attribute von N aus den Attributen der Kinder (wenn N keine Kinder hat, also mit Terminalzeichen markiert, erhält man den Attributwert vom Scanner) Die Prozedur postorder-eval setzt voraus, dass der Syntaxbaum schon vorliegt. Es geht aber auch ohne explizite Konstruktion des Syntaxbaums. Die postorder-reihenfolge passt zum LR-Parser: Die zeitliche Reihenfolge, in der die Zeichen im Keller auftauchen (durch shift oder reduce) entspricht der postorder-reihenfolge im Syntaxbaum. Die Attributwerte lassen sich während des Parsens berechnen, nämlich wie folgt: Zusätzlich zum Zustands und/oder Zeichenkeller führt man einen Keller für die Attributwerte ein: Bei jeder shift-aktion, legt man das Tupel der Attributwerte oben auf den Attributkeller Bei jeder reduce-aktion A X 1... X n berechnet man die Attributwerte für A aus denen von X 1,..., X n (die im Keller oben liegen) und ersetzt letztere durch das Attributtupel für A. L-Attributierte Grammatiken sind (vermutlich) nicht immer mit LR-Parsern verträglich. Da die gängigen Parser-Generatoren LR-Parser generieren, lassen sie nur synthetische Attribute in den semantischen Regeln zu. Mit Tricks kann man geerbte Attribute vermeiden. Beispiel: 7. Attribute inh, syn, val für nicht linksrekursive Grammatik. Man ersetzt inh, syn durch ein synthetisches Attribut fct : int int, das syn in Abhängigkeit von inh berechnet (d.h. inh ist der Parameter von fct, syn ist das Ergebnis von fct). 6
T F T T F T 1 T ɛ T.val = T.fct(F.val) T.fct = λx : int.t 1.fct(x F.val) T.fct = λx : int.x 2 Attrebutauswertung bei L-Attributierten Grammatiken Für jeden Syntaxbaum einer L-Attributierten Grammatik gilt: Aus den geerbten Attributen eines Knotens n lassen sich alle Attribute seiner Kinder und die synthetischen Attribute von n berechnen Beweis. Induktion über die Höhe des Knotens h h = 0 d. h. n ist ein Blatt Die synthetischen Attribute kommen von außen, geerbte sind nicht vorhanden h > 0 d. h. n ist mit Nonterminal X markiert und die Kinder mit X 1,..., X n, wobei X X 1... X n eine Produktion ist. Aus den geerbten von X lassen sich die geerbten von X 1 berechnen (Def. L-Attr.). X X 1... X n Nach Induktionsannahme erhält man daraus die synthetischen von X 1. Daraus erhält man die geerbten Attribute von X 2 (per Def. L-Attr.), bis man alle Attribute von X 1,..., X n kennt. Daraus lassen sich die synthetischen Attribute von X berechnen. Aus dem Beweis ist erkennbar, wie man die Attribute rekursiv berechnen kann und in welcher Reihenfolge sie bei der Rekursion berechnet werden. Sei eval die Auswertungsfunktion zu einer L-Attributierten Grammatik. Die Argumente von eval sind: ein Knoten des Syntaxbaums das Tupel der geerbten Attribute dieses Knotens Das Ergebnis von eval ist das Tupel der synthetischen Attribute des Knotens eval kann wie folgt rekursiv definiert werden: Wenn n ein Blatt mit Terminalzeichen a ist, dann existieren keine geerbten Attribute und eval(n) liefert das Tupel der synthetischen Attribute. 7
Wenn n ein innerer Knoten mit geerbten Attributen inh ist und wenn n i (i = 1,..., k) sind, die Kinder von n mit geerbten Attributen inh i, dann lässt sich eval(n, inh) rekursiv berechnen durch: Algorithmus 2. (1) for i = 1 to k do * berechne inh i mit den semantischen Regeln aus inh und den zuvor berechneten syn j, inh j (j < i) * berechne syn i durch rekursiven Aufruf eval(n i, inh i ) (2) berechne syn (die synthetischen Attribute von n) mit den semantischen Regeln aus den in (1) berechneten Attributen Durch die rekursive Definition von eval ist die Auswertungsreihenfolge definiert. Wenn die geerbten links und die synthetischen rechts stehen, lässt sie sich wie folgt veranschaulichen: Intuition: Man startet mit den geerbten Attributen der Wurzel und geht am Baum entlang bis man bei den synthetischen Attributen der Wurzel ankommt. Umsetzung in ein Programm: Im Aufruf eval(n, inh) hängen Anzahl und Typ der geerbten Attribute, im Tupel inh hängen von der Markierung des Knotens n ab. Man muss eval aufteilen in Funktionen eval X (für jedes Nonterminal X), die sich gegenseitig rekursiv aufrufen. eval X ist zuständig für alle Knoten mit Markierung X. Speziell: Da das Startzeichen S keine geerbten Attribute besitzt, erhält man die synthetischen Attribute der Wurzel n durch den Aufruf eval(n). Beachte: 1. Die rekursive Struktur (Aufrufstruktur) der Funktionen eval X gleiche wie beim Recursive Descent Parser (RDP). ist die Deshalb gilt: Wenn für die KFG ein RDP existiert (z. B. wenn sie LL(1) ist), dann lässt sich die Attributauswertung] (für eine L-Attributierung) in den RDP einbauen. Idee: Die parameterlose Prozeduren A() aus dem RDP ersetzt durch Prozeduren A(inh), die die geerbten Attribute als Parameter haben und die synthetischen Attribute als Ergebnis liefern. Die neuen Prozeduren arbeiten gleichzeitig das Eingabewort ab und berechnen die Attribute. 8
Beispiel: 8. Nicht linksrekursive KFG für arithmetische Ausdrücke mit L-Attributierung (1) T F T T.inh = F.val T.val = T.syn (2) T T T 1 T 1.inh = T.inh F.val T.syn = T 1.syn (3) T ɛ T.syn = T.inh (4) F digit F.val = digit.lexval Laut Parsing-Tabelle: (1) bei Eingabe digit (2) bei Eingabe (3) bei Eingabe $ (4) bei Eingabe digit Prozeduren T (), F () und T (inh) Algorithmus 3. T(): if input digits then T (F()) (denn F () liefert F.val = T.inh Argument für T else error Resultat T.syn = T.val) T (inh): if input = * then move(); T (inh F ()) else if input = $ then inh else error else error F(): let a = input in if a digits then move(); a (F () liefert F.val und T.inh F.val = T 1.inh ist Argument T 1 Resultat T 1.syn = T.syn (wegen T.syn = T.inh) 9
3 Syntaxgerichtete Übersetzungsschemata (ÜS) Englische Bezeichnung: syntax directed translation scheme (SDT) Eine SDT ist eine KFG, bei der Programmstücke in die rechten Seiten von Produktionen eingebetet sind. Die Programmstücke heißen semantische Aktionen. Sie können an jeder Stelle der rechten Seite zwischen {... } auftreten. Beispiel: 9. Umwandlung von Infix- in Präfix-Notation (1) L En (2) E { print + } E 1 + T (3) E T (4) T { print } T 1 F (5) T F (6) F (E) (7) F number { print(number.val)} 3.1 Semantik eines SDT Die Aktionen sind in der Reihenfolge auszuführen, in der sie im Blattwort des Ableitungsbaums auftauchen. Beispiel: 10. Ableitungsbaum für 3 5 + 4n Also mögliche Implementierung: preorder-reihenfolge ausführen. Ableitungsbaum aufbauen, dann die Aktionen in Frage: Kann man SDT s ausführen, ohne den Syntaxbaum aufzubauen, z. B. während LL- oder LR-parsing? Antwort: Im Allgemeinen nicht, siehe Beispiel. 3.2 Übersetzung von S- bzw. L-attributierten Grammatiken in SDT s Eine S-attributierte Grammatik kann in eine sogenannte Postfix-SDT übersetzt werden, bei der alle Aktionen am Ende der Produktion stehen. Beispiel: 11. KFG für arithmetische Ausdrücke: E E 1 + T {E.val = E 1.val + T.val} E T {E.val = T.val}. 10
Postfix-SDT s können (analog zu S-attributierten Grammatiken) während des bottomup-parsing ausgeführt werden: Die semantischen Aktionen, die hinter einer Regel steht, wird gleichzeitig mit den entsprechenden reduce-schritt ausgeführt. Attributwerte werden dabei auf einem Stack verwaltet, der stehts die gleiche Höhe hat, wie der Zeichen- oder Zustandskeller. Eine L-attributierte Grammatik kann in eine SDT übersetzt werden, indem man für jede Produktion folgendes durchführt: 1. Die Aktionen zur Berechnung der geerbten Attribute eines Nonterminals auf der rechten Seite schreibt man unmittelbar vor dieses Nonterminal (wobei man noch auf die Reihenfolge der verschiedenen Attribute dieses Nonterminals achten muss). 2. Die Aktionen zur Berechnung der synthetischen Attribute (zur linken Seite der Produktion) schreibt man ans rechte Ende der Produktion. Beispiel: 12. Nicht linksrekursive KFG für arithmetische Ausdrücke Ableitungsbaum für 3 5: (1) T F {T.inh = F.val} T {T.val = T.syn} (2) T F {T 1.inh = T.inh F.val} T 1 {T.syn = T 1.syn} (3) T ɛ {T.syn = T.inh} (4) F digit {F.val = digit.lexval} 4 Generierung von Zwischencode Hier: Als Zwischencode wird 3-Adresscode verwendet. Befehle dürfen folgende Form haben: 1. Zuweisungen der Form x = y op z wobei op binärer Operator, x,y,z Adressen 2. Zuweisungen der Form wobei op unärer Operator 3. Kopier-Befehl x = op y x = y 4. Unbedingte Sprünge wobei L eine Marke im Programm ist goto L 11
5. Bedingte Sprünge if x goto L if F alse x goto L 6. Bedingte Sprünge mit Vergleich if x relop y goto L wobei relop ein Vergleichsoperator ist (<,,... ) 7. Kopierbefehl mit Indices x = y[i] y[i] = x Dabei ist y[i] der Speicherplatz, der i Einheiten hinter y steht 8. Kopierbefehl mit Adressrechnung x = &y x = y y = x Dabei steht &y für die Adresse y selbst (statt für den Inhalt von y) und y steht für die Adresse, die durch den Inhalt von y gegeben ist. 12
4.1 Übersetzung arithmetischer Ausdrücke in 3-Adress-Code Idee: Bei der Übersetzung eines geschachtelten Ausdrucks müssen genügend temporäre Adressen generiert werden, in denen man Zwischenergebnisse speichert. Befehl new T emp() liefert stehts eine neue temporäre Adresse. Man benutzt 2 Attribute für das Nonterminal E, nämlich: { E.code beide synthetisch E.addr E.code ist der 3-Adress-Code für E. E.addr ist die Adresse, in der E.code das Ergebnis E abliefert. S-Attributierte Grammatik Produktion semantische Regel (1) S id = E S.code = E.code top.get(id.lexem) = E.addr } {{ } aktuelle Adresse für Token id (2) E E 1 + E 2 E.addr = new T emp() E.code = E 1.code E 2.code E.addr = E 1.addr + E 2.addr (3) E E 1 E.addr = new T emp() E.code = E 1.code E.addr = minus E 1.addr (4) E (E 1 ) E.addr = E 1.addr E.code = E 1.code (5) E id E.addr = top.get(id.lexem) E.code = Besser: Anstelle eines Attributs vom Typ string semantische Aktionen benutzen, die den Code ausgeben, d. h. in den Programmspeicher schreiben. 4.2 Übersetzung boolscher Ausdrücke Boolsche Ausdrücke treten in zwei Rollen auf: 1. zur Berechnung boolscher Werte (analog zu arithmetischen Ausdrücken) 2. Zur Steuerung des Kontrollflusses, d. h. als Bedingung in if-then-else, while,... Im Fall 1 übersetzt man sie wie arithmetische Ausdrücke, im Fall 2 generiert man besser sogenannten Jump Code. Jeder boolsche Ausdruck B hat 3 Attribute: 13
B.code synthetisch { B.true geerbt B.f alse B.code ist der 3-Adress-Code für B. B.true ist die Marke, zu der B.code springt, wenn B true liefert. B.false ist die Marke, zu der B.code springt, wenn B false liefert. 4.3 Jumpcode für boolsche Ausdrücke Produktion B B 1 B 2 B B 1 && B 2 B!B 1 B E 1 rel E 2 B true B false semantische Regel B 1.true = B.true B 1.false = newlabel() B 2.true = B.true B 2.false = B.false B.code = B 1.code label(b 1.false) B 2.code B 1.true = newlabel() B 1.false = B.false B 2.true = B.true B 2.false = B.false B.code = B 1.code label(b 1.true) B 2.code B 1.true = B.false B 1.false = B.true B.code = B 1.code B.code = E 1.code E 2.code gen( if E 1.addr rel.op E 2.addr goto B.true) gen( goto B.true) B.code = gen( goto B.true) B.code = gen( goto B.f alse) Erläuterung: B B 1 B 2 : Wenn B 1 = true, dann B = true (ohne dass B 2 ausgewertet wird), also B 1.true = B.true (d. h. beide haben in diesem Fall das gleiche Sprungziel). Wenn B 1 = false, muss B 2 noch ausgewertet werden, also muss man an den Anfang von B 2 springen, also B 2.false = newlabel() wird in B.code verwendet. Wenn B 2 = true bzw B 2 = false, dann ist auch B = true bzw B = false (weil 14
man B 2 nur auswertet, wenn B 1 = false), also werden beide Sprungziele einfach vererbt: B 2.true = B.true, B 2.false = B.false. B!B 1 : B hat gleichen Code wie B 1. Nur die Sprungziele sind vertauscht. B E 1 rel E 2 : E 1.code und E 2.code liefern ihre Resultate in E 1.addr und E 2.addr ab. Also muss man anschließend die Inhalte der beiden Adressen vergleichen (mit rel.op) und in Abhängigkeit vom Ergebnis des Vergleichs zu B.true bzw. B.f alse springen. Wo kommen B.true und B.false her? Entweder von einem größeren boolschen Ausdruck, in dem B enthalten ist oder von der Anweisung, in der B als Bedingung enthalten ist. Anweisungen S haben 2 Attribute, nämlich: S.code S.next synthetisch geerbt vom Typ Label S.next ist die Marke, bei der es nach der Ausführung von S.code weitergeht. Genauer: Die Ausführung von S.code endet entweder mit einem Sprung zu S.next oder sie endet ohne Sprung (so dass es hinter S.code weitergeht). Produktion P S S assign S if( B ) S 1 S if( B ) S 1 else S 2 semantische Regel S.next = newlabel() P.code = S.code label(s.next) S.code = assign.code B.true = newlabel() B.false = S 1.next = S.next S.code = B.code label(b.true) S 1.code B.true = newlabel() B.f alse = newlabel() S 1.next = S 2.next = S.next S.code = B.code label(b.true) S 1.code gen( goto S.next) label(b.false) S 2.code 15
Produktion S while( B ) S 1 S S 1 S 2 semantische Regel begin = newlabel() B.true = newlabel() B.false = S.next S 1.next = begin S.code = label(begin) B.code label(b.true) S 1.code gen( goto begin) S 1.next = newlabel() S 2.next = S.next S.code = S 1.code label(s 1.next) S 2.code Erläuterungen: P S: Mit newlabel() wird das Programmende markiert S.next = Programmende. S assign: Code ist vorgegeben. 16
4.4 Backpatching 4.4.1 Boolsche Ausdrücke 4.4.2 Anweisungen 5 Laufzeitumgebungen 5.1 Speicherorganisation 5.2 Aufteilung zwischen Stack und Heap 5.3 Zugriff auf globale Daten 5.4 Bestimmung des Access Links 5.5 Prozeduren auf Parameterposition 5.6 Alternative zu Access Links: Displays 5.7 calling sequences und return sequences 5.7.1 Design-Prinzipien 5.7.2 Aufteilung der calling sequence 5.7.3 Codeerzeugung für Programme mit Prozeduren (Pascal-artige Sprache) 5.7.4 Vorgehensweise bei der Übersetzung 5.7.5 Übersetzung einer Prozedurdeklaration 5.7.6 Übersetzung eines Prozeduraufrufs 6 Codegenerierung (Drachenbuch Kpt. 8) 6.1 Befehlssatz der Zielmaschine (RISC-Architektur) 6.2 Basisblöcke und Flussgraphen 6.3 Optimierung der Basisblöcke 6.4 Vom (optimierten) DAG zurück zum Zwischencode 6.5 Ein einfacher Codegenerator 6.6 Globale Register Vergabe 7 Instruction Selection (Drachenbuch Kpt. 8.9) 7.1 Generierung von optimalem Code für Ausdrücke 7.1.1 Algorithmus zur Berechnung der Ershov-Zahl 17