400 likes | 525 Views
Vorlesung Compilertechnik Sommersemester 2008. 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:
E N D
Vorlesung CompilertechnikSommersemester 2008 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…Anspeichert 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 := (irlirr "t:=tl + tr", t), wobei t := getNextTemp(), ir1 = (irl,tl) und ir3 = (irr,tr) • ir[Expr,2],0 := (irlirr "t:=tl - tr", t), wobei t := getNextTemp(), ir1 = (irl,tl) und ir3 = (irr,tr) • ir[Expr,3],0 := (irlirr "t:=tl * tr", t), wobei t := getNextTemp(), ir1 = (irl,tl) und ir3 = (irr,tr) • ir[Expr,4],0 := (irlirr "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 ir= t0:=a$0 t1:=2 t2:=b$01 t3:=t1*t2 t4:=t0+t3 c$01:=t4 { int a; … { int b,c; … c := a+2*b; … } … } 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 id=a ir=(t1:=2,t1) ir=(t2:=b$01,t2) INTLIT IDENT id=b iVal=2 Symtab0.1
Ü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; }; Stmt3; Stmt4; } Program ir= 1 BlBegin_1: 2122 BlEnd_1:34 Block { DeclL StmtL } ir= 1 BlBegin_1: 2122 BlEnd_1:34 Stmt1 ; StmtL ir=BlBegin_1: 2122 BlEnd_1:34 ir=1 Stmt2 ; StmtL ir=34 ir=BlBegin_1: 2122 BlEnd_1: Block Stmt3 ; StmtL ir=2122 ir=4 ir=3 { DeclL StmtL } ir=2122 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:= nottc" "iftcnthen 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(T1k1,…,Tnkn). • 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(T1k1,…,Tnkn) 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:
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(T1n1,…,Tmkn), falls lookUp(id1) = struct(T1n1,…,Tmkn) 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(T1n1,…,Tmkn) 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 [ 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(T1n1,…,Tmnm) 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(T1n1,…,Tmnm) und id2 = ni, • ir3 = (irlo,tlo), • to := getNextTemp(), • tno := getNextTemp(). Q ::= . IDENT Q
Beispiel 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 ] ir=( t2:=4,t2) iVal=8 . 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.
DFGs 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) read a read b S const 2 3 2 1 (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 readywird 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