Vorlesung Compilertechnik Sommersemester 2009

Slides:



Advertisements
Ähnliche Präsentationen
8. Termin Teil B: Wiederholung Begriffe Baum
Advertisements

Vorlesung Compilertechnik Sommersemester 2008
Vorlesung Compilertechnik Sommersemester 2009 Zielcodeerzeugung M. Schölzel.
Vorlesung Compilertechnik Sommersemester 2008
Programmierung 1 - Repetitorium WS 2002/2003 Programmierung 1 - Repetitorium Andreas Augustin und Marc Wagner Homepage:
Vorlesung Compilertechnik Sommersemester 2009 Kontextprüfung M. Schölzel.
Vorlesung Compilertechnik Sommersemester 2009 Optimierung M. Schölzel.
Struktur-Funktions-Modelle von Pflanzen - Sommersemester Winfried Kurth Universität Göttingen, Lehrstuhl Computergrafik und Ökologische Informatik.
LS 2 / Informatik Datenstrukturen, Algorithmen und Programmierung 2 (DAP2)
WS Prof. Dr. Th. Ottmann Algorithmentheorie 09 - Suche in Texten KMP, BM.
Zusammenfassung der Vorwoche
7. Natürliche Binärbäume
R. Der - Vorlesung Algorithmen und Datenstrukturen (Magister)
10. Grundlagen imperativer Programmiersprachen
3. Berechenbarkeit Wann ist eine Funktion (über den natürlichen Zahlen) berechenbar? Intuitiv: Wenn es einen Algorithmus gibt, der sie berechnet! Was heißt,
der Universität Oldenburg
Java: Objektorientierte Programmierung
Java: Dynamische Datentypen
Strukturen. In einer Struktur kann eine beliebige Anzahl von Komponenten (Daten) mit unterschiedlichen Datentypen (im Gegensatz zu Feldern) zusammengefaßt.
ARRAY oder FELD oder VEKTOR
Algorithmentheorie 04 –Hashing
Suche in Texten: Suffix-Bäume
WS Algorithmentheorie 02 - Polynomprodukt und Fast Fourier Transformation Prof. Dr. Th. Ottmann.
WS Algorithmentheorie 05 - Treaps Prof. Dr. Th. Ottmann.
1 Vorlesung Informatik 2 Algorithmen und Datenstrukturen (02 – Funktionenklassen) Prof. Dr. Th. Ottmann.
Vorlesung Informatik 2 Algorithmen und Datenstrukturen (02 – Funktionenklassen) Tobias Lauer.
Vorlesung Informatik 2 Algorithmen und Datenstrukturen (02 – Funktionenklassen) Prof. Dr. Th. Ottmann.
Vorlesung Informatik 2 Algorithmen und Datenstrukturen (27 – Kürzeste Wege) Prof. Th. Ottmann.
Vorlesung Informatik 2 Algorithmen und Datenstrukturen (17 – Bäume: Grundlagen und natürliche Suchbäume) Prof. Th. Ottmann.
Union-Find-Strukturen
Algorithmen und Datenstrukturen
WS Prof. Dr. Th. Ottmann Algorithmentheorie 09 - Suche in Texten Suffix - Bäume.
EINI-I Einführung in die Informatik für Naturwissenschaftler und Ingenieure I Vorlesung 2 SWS WS 99/00 Gisbert Dittrich FBI Unido
EINI-I Einführung in die Informatik für Naturwissenschaftler und Ingenieure I Vorlesung 2 SWS WS 99/00 Gisbert Dittrich FBI Unido
EINI-I Einführung in die Informatik für Naturwissenschaftler und Ingenieure I Vorlesung 2 SWS WS 99/00 Gisbert Dittrich FBI Unido
Imperative Programmierung
Klausur „Diskrete Mathematik II“
PKJ 2005/1 Stefan Dissmann Rückblick auf 2005 Was zuletzt in 2005 vorgestellt wurde: Klassen mit Attributen, Methoden und Konstruktoren Referenzen auf.
PKJ 2005/1 Stefan Dissmann Zusammenfassung Bisher im Kurs erarbeitete Konzepte(1): Umgang mit einfachen Datentypen Umgang mit Feldern Umgang mit Referenzen.
PKJ 2005/1 Stefan Dissmann Zusammenfassung der Vorwoche Variable stehen für (einen) Wert, der sich im Programmablauf ändern kann. Variablen besitzen einen.
Zusammenfassung Vorwoche
PKJ 2005/1 Stefan Dissmann Zusammenfassung Vorwoche Methoden sind mit einem Namen versehene Programmabschnitte besitzen Rückgabetyp, Namen, Parameterliste.
Grundlegende Analysen & Zwischendarstellungen
Optimierungstechniken in modernen Compilern
Programmierung 1 - Repetitorium WS 2002/2003 Programmierung 1 - Repetitorium Andreas Augustin und Marc Wagner Homepage:
DVG Ablaufsteuerung
Weiteres Programm Studium des Breitendurchlaufs Hierzu
Wir müssen also überlegen: Implementierung der Knoten, Implementierung der Kanten, daraus: Implementierung des Graphen insgesamt. Annahme: die Knoteninhalte.
LS 2 / Informatik Datenstrukturen, Algorithmen und Programmierung 2 (DAP2)
Einführung in die Programmiersprache C 3.Tag Institut für Mathematische Optimierung - Technische Universität Braunschweig.
Javakurs FSS 2012 Lehrstuhl Stuckenschmidt
Einführung in die Programmierung Wintersemester 2009/10 Prof. Dr. Günter Rudolph Lehrstuhl für Algorithm Engineering Fakultät für Informatik TU Dortmund.
Wiederholte Programmausführung
HORIZONT 1 XINFO ® Das IT - Informationssystem PL/1 Scanner HORIZONT Software für Rechenzentren Garmischer Str. 8 D München Tel ++49(0)89 / 540.
Optimierungstechniken in modernen Compilern
Informatik II Grundlagen der Programmierung Programmieren in C Programmstrukturen / Kontrollstrukturen Hochschule Fulda – FB ET Sommersemester 2014.
Algorithmen und Datenstrukturen Übungsmodul 8
Agenda für heute, 12. Mai, 2005 ProzedurenProzeduren Funktionsprozeduren Prozedurparameter Lokale und globale Variablen Datentypen: Ordinaltypen.
Agenda für heute, 14. April, 2005 Wiederholte ProgrammausführungWiederholte Programmausführung Algorithmische Grundlagen Bedingungen zum Abbruch von Programmschleifen.
Programmieren in C Grundlagen C 2
BMEVIEEA100 Grundlagen der Programmierung
PHP: Operatoren und Kontrollstrukturen
1 Albert-Ludwigs-Universität Freiburg Rechnernetze und Telematik Prof. Dr. Christian Schindelhauer Informatik III Christian Schindelhauer Wintersemester.
Parallelisierung für Multiprozessor-Maschinen
Code-Optimierung Philipp Bergener Seminar „Übersetzung künstlicher Sprachen“
Agenda Motivation und Einordnung Syntaxgerichtete Übersetzung
Wann ist eine Funktion (über den natürlichen Zahlen) berechenbar?
Dr. Wolfram Amme, Semantik funktionaler Programme, Informatik II, FSU Jena, SS Semantik funktionaler Programme.
Funktionen (Zweck und Eigenschaften) Funktionen sind Unterprogramme, die einen bestimmten Zweck erfüllen Sie zerlegen Probleme in kleine, abgeschlossene.
 Präsentation transkript:

Vorlesung Compilertechnik Sommersemester 2009 Zwischencodeerzeugung M. Schölzel

Aufgabe der Zwischencodeerzeugung Bereitstellung einer Schnittstelle zum Backend des Compilers. Generierung einer oder mehrerer Zwischenrepräsentation für das Quellprogramm, so dass: alle erforderlichen Informationen erhalten bleiben, die benötigten Informationen auf eine geeignete Weise für den jeweiligen Zweck dargestellt sind, keine oder wenig zielcodespezifische Informationen enthalten sind, sich die Darstellung der Informationen gut in Zielcode/die nächste Zwischencoderepräsentation übersetzen lässt. Art der Zwischenrepräsentation hängt stark vom jeweiligen Zweck ab und kann damit stark variieren.

Beispiele für oft genutzte Arten der Zwischencodedarstellung C (erster C++ Compiler, Bison, Flex): Darstellung mehrerer Module, jedes davon besteht aus Funktionen. Syntaxbaum: In der Regel Darstellung eines Moduls mit mehreren Funktionen. Aufrufgraph: Darstellung von Funktionen und der Aufrufbeziehungen. Steuerflussgraph: Repräsentation des Steuerflusses innerhalb einer Funktion. CDFG: Repräsentation einer Funktion und der Datenabhängigkeiten. DAGs: Repräsentation von Datenabhängigkeiten ohne Steuerfluss. 3-Adress-Code: Repräsentation mehrerer Funktionen. SSA-Code: Repräsentation mehrere Funktionen. …

Einbettung der Zwischencodeerzeugung in den Compiler Da der Syntaxbaum schon eine Zwischencodeart darstellt, ist die Erzeugung des Syntaxbaums bereits Teil der Zwischencodeerzeugung. Dieser kann in mehreren Schritten vereinfacht werden. Die Darstellung des Zwischencodes nähert sich dabei immer mehr dem gewünschten Zielcode an. Abstraktionsebene Hoch Niedrig Syntaxbaum 3-Adress-Code Parser … Zielcode- erzeugung t2 := t1 + t0 t3 := a t4 := t2 * t3 … Zwischencodeerzeugung

3-Adress-Code Folge von 3-Adress-Code-Anweisungen. Anweisungsarten: Binäranweisungen: x := y  z Unäranweisungen: x :=  y Kopieranweisungen: x:=y, x:=k, @x:=y, x:=@y, x:=&y Sprunglabel: Label: Funktionslabel: Function Label: Unbedingte Sprünge: goto index Bedingte Sprünge: if x then Label Funktionsaufrufe: x := call FLabel(y1,…,yn) Funktionsbeendigung: return x, return Castoperationen: x := (Type) y Dabei sind: k Konstante, x, y, yi und z Variablen, wobei Variablen unterschieden werden in: Programmvariablen (lokal, global) Temporäre Variablen (immer lokal)

Beispiel: 3-Adress-Code Function f: t0 := 1 fak = t0 while_0_cond: t1 := n t2 := 0 t3 := t1 > t2 t4 := not t3 if t4 then while_0_end t5 := fak t6 := n t7 := t5 * t6 fak := t7 t8 := n t9 := 1 t10 := t8 – t9 n := t10 goto while_0_cond while_0_end: t11 := fak return t11 int f(int n){ int fak = 1; while(n > 0) { fak = fak * n; n = n – 1; } return fak;

Speichermodell Temporäre Variablen repräsentieren Werte, die bevorzugt in Prozessorregistern gehalten werden. Programmvariablen repräsentieren Werte, die im Hauptspeicher gehalten werden und damit auch eine Speicheradresse besitzen. Es wird unterschieden zwischen Programmvariablen mit: einer absoluten Adresse (global), einer relativen Adresse (lokal). Parametern einer Funktion und lokalen Variablen einer Funktion, die nicht Parameter sind. Absolute und relative Adressen (möglicherweise als virtuelle Adresse) werden bereits während der Zwischencodeerzeugung festgelegt und zu jeder Variablen gespeichert.

Modell für die Zwischencodeerzeugung Die LR(1)-Grammatik der Quellsprache wird für die Zwischencodeerzeugung zu einer attributierten Grammatiken erweitert: S-attributierte Grammatik zum Aufbau des Syntaxbaums: Jedes Attributvorkommen in einer Regel A0  A1…An speichert die Wurzel des Syntaxbaums, der während der Analyse zu der Ableitung Ai *  gehört. Der LR-Parser kann diese Attribute während der Analyse direkt auswerten und berechnet somit den Syntaxbaum. S- oder L-attributierte Grammatik zur Erzeugung von 3-Adress-Code: Jeder Knoten erhält ein Attribut, das ein Zwischencodefragment speichern kann. Durch geeignete semantische Funktionen werden diesen Attributen Werte zugewiesen. Konsequenz: An Syntaxbaumknoten, an denen dieselbe Grammatik-regel angewendet wurde, wird auch dieselbe Aktion ausgeführt. Weitere Annahme: Hierarchisch organisierte Symboltabellen sind bereits erzeugt.

Prinzipien bei der Übersetzung eines Syntaxbaums in 3-Adress-Code Anweisungen ändern Speicherzustände oder den Steuerfluss: Übersetzung in entsprechende Zwischencodebefehle. Übersetzung von Ausdrücken aus der Quellsprache: Bedeutung ist ein Wert. Erzeugung von Zwischencode mit derselben Bedeutung. Berechnung des Wertes in eine temporäre Variable. Ausdrucksanweisungen ändern Speicherzustände und haben einen Wert als Bedeutung. Syntaxbaumknoten, die zu einem Ausdruck gehören können, speichern neben dem Zwischencodefragment auch die temporäre Variable, in die der Zwischencode den Wert berechnet. Attribute für Syntaxbaumknoten, die zu einem Ausdruck gehören können: (ir,t): ir Speichert den 3-Adress-Code und t die Zwischencodevariable, in die durch ir der Wert des Ausdrucks berechnet wird. iVal, fVal sind synthetisiertes Attribute an INTLIT bzw. FLOATLIT, deren Wert durch den Scanner gesetzt wird. Bereits vorhanden: t speichert den Typ des Ausdrucks.

Hilfsfunktionen, für die Übersetzung in 3-Adress-Code getNextTemp(): liefert einen neuen, noch nicht benutzten Namen für eine temporäre Variable zurück. getNextWhile(): liefert eine neue, noch nicht benutzten Nummer für eine while-Schleife zurück. getNextIf(): liefert eine neue, noch nicht benutzten Nummer für eine if-Anweisunge zurück. uniqueName(v): Liefert für die Programmvariable v ihren eindeutigen Namen durch Suchen in den hierarchisch organisierten Symboltabellen. Eindeutiger Name entsteht z.B. durch Erweiterung des Variablennamens mit der lexikografischen Nummer der Symboltabelle. nextGlobalAdress: Nächste freie globale Adresse (relativ zu einer virtuellen Basis). Kann bereits beim Aufbau der globalen Symboltabelle festgelegt werden. nextLocalAdress: Nächste freie lokale Adresse (relativ zu einer virtuellen Basisadresse). Kann lokal für jede Symboltabelle zu einer Funktion festgelegt werden.

Übersetzung von Zuweisungen und Ausdrücken ir[Expr,6],0 := ("t:=a", t), wobei t := getNextTemp() und a = uniqueName(id1) ir[Expr,8],0 := ("t:=iVal1", t), wobei t := getNextTemp() ir[Expr,9],0 := ("t:=fVal1", t), wobei t := getNextTemp() ir[Expr,7],0 := (ir  "t:=(type)t'", t), wobei t := getNextTemp(), ir4 = (ir,t'), type = t2 ir[Expr,5],0 := ir2 ir[Expr,1],0 := (irl  irr  "t:=tl + tr", t), wobei t := getNextTemp(), ir1 = (irl,tl) und ir3 = (irr,tr) ir[Expr,2],0 := (irl  irr  "t:=tl - tr", t), wobei t := getNextTemp(), ir1 = (irl,tl) und ir3 = (irr,tr) ir[Expr,3],0 := (irl  irr  "t:=tl * tr", t), wobei t := getNextTemp(), ir1 = (irl,tl) und ir3 = (irr,tr) ir[Expr,4],0 := (irl  irr  "t:=tl / tr", t), wobei t := getNextTemp(), ir1 = (irl,tl) und ir3 = (irr,tr) id[LVal,1],0 := id1 ir[Assign,1],0 := ir  " t:=t' ", wobei t = uniqueName(id1), ir3 = (ir,t')

Beispiel Syntaxbaum für den Ausdruck c := a+2*b: Quelltextfragment: Assign { int a; … int b,c; c := a+2*b; } ir= t0:=a$0 t1:=2 t2:=b$01 t3:=t1*t2 t4:=t0+t3 c$01:=t4 ir=( t0:=a$0 t1:=2 t2:=b$01 t3:=t1*t2 t4:=t0+t3,t4) LVal Expr id=c$01 IDENT id=c Expr Expr ir=(t0:=a$0,t0) ir=( t1:=2 t2:=b$01 t3:=t1*t2,t3) Symtab0 IDENT Expr Expr Name UniquName Typ Scope Adresse a a$0 int lokal id=a ir=(t1:=2,t1) ir=(t2:=b$01,t2) INTLIT IDENT iVal=2 id=b Symtab0.1 Name UniquName Typ Scope Adresse b b$01 int lokal -4 c c$01 -8

Übersetzung von Anweisungsfolgen ir[Stmt,1] := "BlBegin_i:"  ir1  "BlEnde_i", wobei i = getNextBlock(). Einfügen von Labeln, um an den Blockgrenzen auch Basisblöcke zu abzuschließen. Für die übrigen Alternativen 2,…,k zu Stmt: ir[Stmt,k] := ir1, für 1 < k. ir[StmtL,1] := ir1  ir3 ir[StmtL,2] :=  ir[Block,1] := ir3 ir[Program,1] := ir1

Beispiel Syntaxbaum für das Programm: { Stmt1; Stmt21; Stmt12; }; ir= 1 BlBegin_1: 2122 BlEnd_1:34 Block { DeclL StmtL } ir= 1 BlBegin_1: 2122 BlEnd_1:34 Stmt1 ; StmtL ir=BlBegin_1: 2122 BlEnd_1:34 ir=1 Stmt2 ; StmtL ir=34 ir=BlBegin_1: 2122 BlEnd_1: Block Stmt3 ; StmtL ir=2122 ir=4 ir=3 { DeclL StmtL } ir=2122 Stmt4 ; StmtL ir=4 Stmt21 ; StmtL ir=21 ir=22 Stmt22 ; StmtL ir=22

Erweiterung der Grammatik um Schleifen und bedingte Verzweigungen Program ::= Block Block ::= { DeclL StmtL } DeclL ::= Type VarL ; DeclL |  Type ::= TYPELIT VarL ::= IDENT , VarL | IDENT StmtL ::= Stmt ; StmtL |  Stmt ::= Block | Assign | While | If Assign ::= LVal = Expr LVal ::= IDENT Expr ::= Expr + Expr | Expr - Expr | Expr * Expr | Expr / Expr | ( Expr ) | IDENT | ( Type ) Expr | INTLIT | FLOATLIT While ::= while Cond Block If ::= if Cond then Block else Block Cond ::= …

Übersetzung einer While-Anweisung ir[While,1],0 := "while_i_cond:"  irc  "tcn:= not tc"  "if tcn then goto while_i_end"  irb  "goto while_i_cond"  "while_i_end:", wobei: ir2 = (irc,tc), ir3 = irb, tcn = getNextTemp(), i = getNextWhile().

Übersetzung einer if-Anweisung ir[If,1],0 := (irc  "if tc then goto then_i"  irb2  "goto if_i_end"  "then_i:"  irb1  "if_i_end:"), wobei: ir2 = (irc,tc), ir4 = irb1, ir6 = irb2, i = getNextIf().

Typkonstruktoren Es sei B = {int, float} die Menge der Basisdatentypen. Zu jedem Programm gehört eine Menge T von Typen mit B  T, die auch selbst definierte Datentypen enthält. Es existieren die Typkonstruktoren: array(T, n), wobei T   ein Datentyp ist und n  , struct(T1 k1,…,Tn kn). Für T   stehen folgende Hilfsfunktionen bereit: sizeof(T): Speicherbedarf des Datentyps T in Byte. typeOfElem(T) = T', falls T = array(T', n). typeOfElem(T,k) = Ti, falls T = struct(T1 k1,…,Tn kn) und ki = k. Für einen Variablenbezeichner a: lookUp(a) = T, falls T der Datentyp des Bezeichners a ist.

Deklarationen eigener Datentypen Grammatik erweitert um Deklaration eigener Datentypen: Program ::= TypeDeclL Block TypeDeclL ::= TypeDecl ";" TypeDeclL |  TypeDecl ::= IDENT = NewType NewType ::= IDENT | "array" [INTLIT] "of" NewType | "struct" { NewTypeL } newTypeL ::= NewType IDENT| NewType IDENT , NewTypeL Block ::= { DeclL StmtL } Beispiel zur Deklaration eigener Datentypen: myint = int; a1int = array [10] of int; a2int = array [10] of array [5] of int; t = array [20] of struct {int a, a2int b}; Beim Parsen erzeugte Datentyptabelle: Typbezeichner Typkonstruktor Größe in Byte myint int 4 a1int array (int, 10) 40 anonym_1 array (int, 5) 20 a2int array (anonym_1,10) 200 anonym_2 struct (int a, a2int b) 204 t array (anonym_2,20) 4080

Attributierte Grammatik zur Übersetzung von Struktur- und Feldzugriffen Stmt ::= Block | Assign | While | If Assign ::= LVal = Expr LVal ::= IDENT Expr ::= Expr + Expr | Expr - Expr | Expr * Expr | Expr / Expr | ( Expr ) | IDENT | ( Type ) Expr | INTLIT | FLOATLIT | IDENT Q While ::= while Cond Block Q ::= [ Expr ] Q | . IDENT Q | [ Expr ] | . IDENT

Beispiel Für Felder und Strukturen sind folgende Informationen Im Syntaxbaum annotiert: In der Regel Expr  IDENT Q ist t2 = array(T,n), falls lookUp(id1) = array(T,n) ist. In der Regel Expr  IDENT Q ist t2 = struct(T1 n1,…,Tm kn), falls lookUp(id1) = struct(T1 n1,…,Tm kn) ist. In der Regel Q  [ Expr ] Q ist t4 = T', falls t0 = array(T',n). In der Regel Q  . IDENT Q ist t3 = Ti, falls t0 = struct(T1 n1,…,Tm kn) und id2 = ki. Beispiel: Expr Deklaration: t i; Zugriff: i[15].b[8][4]; IDENT Q id=i t=array(anonym_2,20) [ Expr ] Q t=struct(int a, a2int b) INTVAL Q iVal=15 . IDENT t=array(anonym_1,10) id=b Typbezeichner Typkonstruktor myint int a1int array (int, 10) anonym_1 array (int, 5) a2int array (anonym_1,10) anonym_2 struct (int a, a2int b) t array (anonym_2,20) [ Expr ] Q t=array(int, 5) INTVAL [ Expr ] iVal=8 . INTVAL iVal=4 .

Übersetzung von Feld- und Strukturzugriffen in Ausdrücken (1) Expr ::= IDENT Q ir[Expr,10],0 := ( iro  "tb:=&id1"  "tp:=tb+to"  "t:=@tp", t), wobei (iro,to) = ir2, tb := getNextTemp(), tp := getNextTemp(), t := getNextTemp(). ir[Q,3],0 := ( ire  "ts:=s"  "to:=ts*te", to), wobei ir2 = (ire,te) t0 = array(T',n) und s = sizeof(T') ts := getNextTemp() to := getNextTemp() ir[Q,4],0 := ( "to:=off", to), wobei off = , falls t0 = struct(T1 n1,…,Tm nm) und id2 = ni to := getNextTemp(). Q ::= [ Expr ] Q ::= . IDENT

Übersetzung von Feld- und Strukturzugriffen in Ausdrücken (2) Q ::= [ Expr ] Q ir[Q,1],0 := (ire  irlo  "ts:=s"  "to:=ts*te"  "tno:=to+tlo", tno), wobei ir2 = (ire,te), ir4 = (irlo,tlo), t0 = array(T',n) und s = sizeof(T'), ts := getNextTemp(), tno := getNextTemp(), to := getNextTemp(). ir[Q,2],0 := (irlo  "to:=off"  "tno:=to+tlo", tno), wobei off = offset(ni), falls t0 = struct(T1 n1,…,Tm nm) und id2 = ni, ir3 = (irlo,tlo), to := getNextTemp(), tno := getNextTemp(). Q ::= . IDENT Q

Beispiel Expr IDENT Q [ Expr ] Q INTVAL Q . IDENT [ Expr ] Q INTVAL ir=( t0:=15 t1:=8 t2:=4 t3:=4 //sizeof(int) t4:=t3*t2 t5:=20 t6:=t5*t1 t7:=t6+t4 t8:=4 //offset b t9:=t8+t7 t10:=204 t11:=t10*t0 t12:=t11+t9 t13:=&i t14:=t13+t12, t15:=@t14,t15) ir=( t0:=15 t1:=8 t2:=4 t3:=4 //sizeof(int) t4:=t3*t2 t5:=20 //sizof(anonym_1) t6:=t5*t1 t7:=t6+t4 t8:=4 //offset b t9:=t8+t7 t10:=204 //sizeof(anonym_2 t11:=t10*t0 t12:=t11+t9,t12) ir=( t0:=15 t1:=8 t2:=4 t3:=4 //sizeof(int) t4:=t3*t2 t5:=20 //sizof(anonym_1) t6:=t5*t1 t7:=t6+t4 t8:=4 //offset b t9:=t8+t7 t10:=204 //sizeof(anonym_2 t11:=t10*t0 t12:=t11+t9,t12) ir=( t1:=8 t2:=4 t3:=4 //sizeof(int) t4:=t3*t2 t5:=20 //sizof(anonym_1) t6:=t5*t1 t7:=t6+t4 t8:=4 //offset b t9:=t8+t7,t9) Expr IDENT Q id=i t=array(anonym_2,20) [ Expr ] Q t=struct(int a, a2int b) ir=( t0:=15,t0) INTVAL Q ir=( t1:=8 t2:=4 t3:=4 //sizeof(int) t4:=t3*t2 t5:=20 //sizof(anonym_1) t6:=t5*t1 t7:=t6+t4,t7) iVal=15 . IDENT t=array(anonym_1,10) id=b ir=( t2:=4 t3:=4 //sizeof(int) t4:=t3*t2,t4) [ Expr ] Q ir=( t1:=8,t1) t=array(int, 5) INTVAL [ Expr ] iVal=8 . ir=( t2:=4,t2) INTVAL iVal=4 .

Grammatik zur Übersetzung von Funktionsaufrufen und -deklarationen Program ::= TypeDeclL FuncL FuncL ::= IDENT IDENT ( ) Block | IDENT IDENT ( FormalParam ) Block FormalPram ::= IDENT IDENT , FormalParam | IDENT IDENT Block ::= { DeclL StmtL } … Expr ::= Expr + Expr | Expr - Expr | Expr * Expr | Expr / Expr | ( Expr ) | IDENT | ( Type ) Expr | INTLIT | FLOATLIT | IDENT Q | IDENT ( ParamList ) | IDENT ( ) ParamL ::= Expr | Expr , ParamL While ::= while Cond Block Q ::= [ Expr ] Q | . IDENT Q | [ Expr ] | . IDENT

Übersetzung einer Deklaration Eine Funktion funcDecl speichert die Signaturen der im Programm deklarierten Funktionen: Rückgabetyp, Name, Typen der formalen Parameter. Eine Deklaration der Art t f(t1 i1,…,tn in) im Programm führt zu einem Eintrag (f, (t, t1, …,tn)) in funcDecl. Leicht durch geeignete Attribute zu realisieren.

Übersetzung von Funktionsaufrufen ParamL erhält ein Attribut pl zur Speicherung der aktuellen Parameterliste und ein Attribut ir zur Speicherung des Zwischencodes, der bei der Übersetzung der Ausdrücke in der Parameterliste entstanden ist: pl[ParamL,1],0 := te und ir[ParamL,1],0 := ire, wobei (ire,te) = ir1. pl[ParamL,2],0 := (te ,pl3) und ir[ParamL,2],0 := ire  ir3, wobei (ire,te) = ir1. Expr ir=( '    tr := call f(t',t), tr) IDENT ( ParamList ) id=f pl=(t', t) ir= '   Expr , , ParamList ir=(',t') pl=t ir= Expr ir=(,t)

Basisblöcke Ein Basisblock ist eine Folge maximaler Länge von Anweisungen im 3-Adress-Code, für die gilt: Nur die erste Anweisung darf ein Label sein (d.h., dass ein Sprung in einen Basisblock nur zu seiner ersten Anweisung führen kann) und nur die letzte Anweisung darf eine Sprunganweisung sein (d.h., dass alle Anweisungen des Basisblocks ausgeführt werden, wenn die erste Anweisung ausgeführt wird). Anmerkung: Unterprogrammaufrufe können als k-näre Operation betrachtet werden, falls sie keine Seiteneffekte verursachen. return-Anweisungen sind Sprunganweisungen. Der erzeugte unoptimierte Zwischencode hat folgende nützlichen Eigenschaften: Vor jeder Benutzung einer temporären Variablen wird diese im selben Basisblock beschrieben. Nachdem eine temporäre Variable beschrieben wurde, wird sie genau einmal im selben Basisblock benutzt. Programmvariablen treten nur in Anweisungen der Art x := y auf, wobei entweder x oder y eine Programmvariable ist. Es ist eine totale Ordnung für die Anweisungen innerhalb eines Basisblocks vorgegeben.

DAGs zur Repräsentation von Basisblöcken Totale Ordnung einer Anweisungsfolge im 3-Adress-Code wird zu einer partiellen Ordnung abgeschwächt. G = (N, E, A, ord, label) sei ein gerichteter azyklischer Graph (DAG): Knoten repräsentieren Operationen in den 3-Adress-Code-Anweisungen. Kanten in E repräsentieren durch Variablen modellierte Datenabhängigkeiten: Lese-Schreib-Abhängigkeit (input-dependence), Kanten in A repräsentieren durch Speicherzugriffe entstehende Datenabhängigkeiten: Schreib-Lese-Abhängigkeit (anti-dependence), Schreib-Schreib-Abhängigkeit (output-dependence) ord : E   modelliert die Reihenfolge der eingehenden Kanten (Operanden) eines Knotens. Bei ord(e) < ord(e') ist e linker und e' rechter Operand. label : N  {const k, store, load, write a, read a,  | k  , a  +,  ist Operation im 3-Adress-Code} ist eine Beschriftung der Knoten mit Operationen.

Konstruktion eines DAGs zu einem Basisblock mit Eliminierung gemeinsamer Teilausdrücke Eingabe: Basisblock als Folge von 3-Adress-Code-Anweisungen ir0,…,irn Ausgabe: DAG (N, E, A, ord, label) Algorithmus: Hilfsfunktionen: N := , E := , A := , ord := , label :=  S :=  // Enthält für die aktuelle Situation bei der Übersetzung für jede Variable // des Zwischencodes u.a. den Knoten im DAG, der ihren Wert berechnet for i = 0 to n do switch(iri) case "x := y  z": TranslateBinStmt(iri); break; case "x :=  y : TranslateUnaStmt(iri); break; case "x := y" : TranslateCopy(iri); break; case "@x := y" : TranslateStore(iri); break; case "x := @y" : TranslateLoad(iri); break; end od Für jedes (a,n,W)  S mit a ist Programmvariable erzeuge Knoten m mit label(m) = write a, N := N  {m}, E := E  {(n,m)}, A := A  {(h,m) | label(h) = read a oder label(h) = load oder label(h) = store} findVar(var) if (var,n,x)  S then return n else return 0 fi findLabel(label,l,r) if n  N mit Beschriftung label und ((l,n)  E oder l = 0) und ((r,n)  E oder r = 0) then return n else return 0 fi

Übersetzung von Kopieranweisungen TranslateConst(x := k) if findLabel(const k,0,0) = 0 then Erzeuge Knoten n mit label(n) = const k N := N  {n} fi n := findLabel(const k,0,0) S := S  {(x,n,W)} TranslateCopy(x := y) if findVar(y) = 0 then Erzeuge Knoten n mit label(n) = read y // passiert nur, wenn y Programmvariable N := N  {n} S := S  {(y,n,R)} fi l := findVar(y) S := S – {(x,n,k) | n  N und k  {R,W}) S := S  {(x,l,W)}

Übersetzung binärer und unärer Operationen TranslateUnaStmt(x :=  y) l := findVar(y) // immer erfolgreich if findLabel(, l) then m := n else Erzeuge neuen Knoten m mit label(m) =  N := N  {m} E := E  {(l,m)} fi S := S – {(x,n,k) | n  N und k  {R,W}) S := S  {(x,m,W)} TranslateBinStmt(x := y  z) l := findVar(y) r := findVar(z) if  n  N mit label(n) =  und (l,n)  E und (r,n)  E und not ((r,n) < (l,n)) then m := n else Erzeuge einen Knoten m mit Beschriftung  N := N  {m} E := E  {(l,m),(r,m)} ord((l,m)) := 0; ord((r,m)) := 0, falls  kommutativ, sonst ord((r,m)) := 1 fi S := S – {(x,n,k) | n  N und k  {R,W}) S := S  {(x,m,W)}

Beispiel 1 Beispiel: a = 2*(b+a-2) * (b+a) const 2 read b read a S 1 2 3 (t0,1,W) t0 := 2 t1 := b t2 := a t3 := t1+t2 t4 := 2 t5 := t3 – t4 t6 := t0 * t5 t7 := b t8 := a t9 := t7 + t8 t10 := t6 * t9 a := t10 (b,2,R) + (t1,2,W) 4 (a,3,R) - (t2,3,W) 5 (t3,4,W) (t4,1,W) * (t5,5,W) 6 (t6,6,W) (t7,2,W) (t8,3,W) (t9,4,W) * (t10,7,W) 7 (a,7,W) write a 8

Übersetzung von Speicherzugriffen TranslateLoad(x := @y) l := findVar(y) Erzeuge neuen Knoten n mit label(n)=load N := N  {n} E := E  {(l,n)} S := S – {(x,n,k) | n  N und k  {R,W}) S := S  {(x,n,W)} A := A  {(k,n) | k  N und label(k) = store oder label(k) = write a} TranslateStore(@x := y) l := findVar(x) r := findVar(y) Erzeuge neuen Knoten n mit label(n)=store N := N  {n} E := E  {(l,n),(r,n)}; ord((l,n)):=0; ord((r,n)):=1; A := A  {(k,n) | k  N und label(k)=store oder label(k)=load oder label(k) = read a}

Beispiel 2 S Beispiel: a[i] = b[i] + a[j] (i,1,R) (t0,1,W) read j t0 := i t1 := 4 t2 := t1 * t0 t3 := &b t4 := t3 + t2 t5 := @t4 t6 := j t7 := 4 t8 := t7 * t6 t9 := &a t10 := t9 + t8 t11 := @t10 t12 := t5 + t11 t13 := i t14 := 4 t15 := t14 * t13 t16 := &a t17 := t16 + t15 @t17 := t12 read i const 4 (t1,2,W) 7 1 2 (t2,3,W) (t3,4,W) * const &b * const &a (t4,5,W) 3 4 8 9 (t5,6,W) (j,7,R) + + + (t6,7,W) 5 13 10 (t7,2,W) (t8,8,W) (t9,9,W) load load 6 11 (t10,10,W) (t11,11,W) + (t12,12,W) 12 (t13,1,W) (t14,2,W) (t15,3,W) (t16,9,W) store 14 (t17,13,W)

Rücktransformation (List-Scheduling) Transformation eines DAGs (N, E, A, ord, label) in 3-Adress-Code ist trivial: Eine Menge ready speichert Knoten, deren Eingabedaten berechnet wurden. Eine Menge scheduled speichert die geplanten Knoten. Initial: scheduled :=  Genau die Kanten e  E mit demselben Quellknoten werden mit derselben temporären Variablen beschriftet. ready = {n | n  N und m  (N – scheduled): (m,n)  E und (m,n)  A}. Für einen Knoten n  ready wird die zugehörige 3-Adress-Code-Anweisung mit den Operanden an den ein- und ausgehenden Kanten von n erzeugt und scheduled := scheduled  {n} gesetzt. Der letzte Schritt wird solange wiederholt, bis scheduled = N. Konsequenz: Variablen werden mehrfach verwendet. Reihenfolge der Operationen ist nur partiell festgelegt und kann durch die Auswahl des nächsten Knotens aus ready beeinflusst werden (evtl. Nutzung einer Prioritätsfunktion).

Steuerflussgraph Eine Folge von 3-Adress-Code-Befehlen sei in eine Menge von Basisblöcken b1,…bn zerlegt. Ein Basisblock bj ist Steuerflussnachfolger eines Basisblocks bi, gdw. (die letzte Anweisung in bi kein Sprungbefehl oder ein bedingter Sprungbefehl ist und im 3-Adress-Code die erste Anweidung von bj auf die letzte Anweisung von bi folgt) oder die letzte Anweisung in bi ein Sprungbefehl mit dem Ziellabel x ist und die erste Anweisung in bj das Label x ist. Ein Steuerflussgraph (N,E,q,s) ist ein gerichteter Graph: dessen Knoten Basisblöcke repräsentieren und der eine Kante vom Knoten n zum Knoten m besitzt, falls m Steuerflussnachfolger von n ist. q,s  N sind ausgezeichnete Startknoten / Endknoten. Ein Steuerflussgraph enthält alle möglichen Abarbeitungspfade innerhalb einer Prozedur. Wird verwendet zur Sammlung von Informationen im Programm, um diese für Optimierungszwecke zu nutzen.

Statischer Aufrufgraph In einem statischen Aufrufgraphen (N,E,label) repräsentieren die Knoten die Funktionen des Programms. Eine gerichtete Kante von einem Knoten n zu einem Knoten m existiert genau dann, wenn die Funktion f die Funktion f' aufruft und label(n) = f und label(m) = f'. Verwendung zur interprozeduralen Datenflussanalyse und Programmoptimierung.

SSA-Code (Static-Single-Assignment) SSA-Code ist 3-Adress-Code mit folgenden Eigenschaften bzw. Erweiterungen: Eigenschaft: Jeder Variablen wird statisch nur einmal ein Wert zugewiesen. Erweiterung: Es gibt eine Operation x := (y1,…yn), die, abhängig vom Programmablauf, der zu dieser Operation geführt hat, der Variablen x den Wert einer Variablen yi zuordnet. Datenabhängigkeiten sind direkt erkennbar, da jede Variablenverwendung genau eine Definition besitzt. a := 2 b := a a:= 1 a0 := 2 b := a0 a1:= 1 a:= 2 a2:= 2 d:= a d:= (a1,a2) Steuerflussgraph mit 3-Adress-Code Steuerflussgraph mit SSA-Code

Ende der Zwischencodeerzeugung Weiter zur Zielcodeerzeugung