Einführung - WWW-Docs for TU

Werbung
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:





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.
2
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.
…
3
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
Syntaxbaum
Parser
Niedrig
3-Adress-Code
…
t2 := t1 + t0
t3 := a
t4 := t2 * t3
…
Zwischencodeerzeugung
Zielcodeerzeugung
4
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)
5
Beispiel: 3-Adress-Code
int f(int n){
int fak = 1;
while(n > 0) {
fak = fak * n;
n = n – 1;
}
return fak;
}
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
6
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.
7
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:



S- oder L-attributierte Grammatik zur Erzeugung von 3-Adress-Code:




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.
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 Grammatikregel angewendet wurde, wird auch dieselbe Aktion ausgeführt.
Weitere Annahme: Hierarchisch organisierte Symboltabellen sind
bereits erzeugt.
8
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.
9
Hilfsfunktionen, für die Übersetzung in 3Adress-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 ifAnweisunge 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.
10
Übersetzung von Zuweisungen und
Ausdrücken

ir[Expr,6],0 := ("t:=a", t),

ir[Expr,8],0 := ("t:=iVal1", t),
ir[Expr,9],0 := ("t:=fVal1", t),
ir[Expr,7],0 := (ir  "t:=(type)t'", t),



ir[Expr,5],0 := ir2
ir[Expr,1],0 := (irl  irr  "t:=tl + tr", t),

ir[Expr,2],0 := (irl  irr  "t:=tl - tr", t),

ir[Expr,3],0 := (irl  irr  "t:=tl * tr", t),

ir[Expr,4],0 := (irl  irr  "t:=tl / tr", t),



id[LVal,1],0 := id1
ir[Assign,1],0 := ir  " t:=t' ",
wobei t := getNextTemp() und
a = uniqueName(id1)
wobei t := getNextTemp()
wobei t := getNextTemp()
wobei t := getNextTemp(),
ir4 = (ir,t'), type = t2
wobei t := getNextTemp(),
ir1 = (irl,tl) und ir3 = (irr,tr)
wobei t := getNextTemp(),
ir1 = (irl,tl) und ir3 = (irr,tr)
wobei t := getNextTemp(),
ir1 = (irl,tl) und ir3 = (irr,tr)
wobei t := getNextTemp(),
ir1 = (irl,tl) und ir3 = (irr,tr)
wobei t = uniqueName(id1),
ir3 = (ir,t')
11
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;
…
}
…
LVal id=c$01
IDENT id=c
}
Expr
Expr ir=(t0:=a$0,t0)
ir=( t0:=a$0
t1:=2
t2:=b$01
t3:=t1*t2
t4:=t0+t3,t4)
Expr
Symtab0
Name UniquName
a
a$0
Typ
Scope
Adresse
int
lokal
0
IDENT id=a
ir=( t1:=2
t2:=b$01
t3:=t1*t2,t3)
Expr ir=(t1:=2,t1)
Expr ir=(t2:=b$01,t2)
INTLIT iVal=2
IDENT id=b
Symtab0.1
Name UniquName
Typ
Scope
Adresse
b
b$01
int
lokal
-4
c
c$01
int
lokal
-8
12
Übersetzung von Anweisungsfolgen







"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
ir[Stmt,1] :=
13
Beispiel
Syntaxbaum für das Programm:
{
Stmt1;
{
Stmt21;
Stmt12;
};
Stmt3;
Stmt4;
}
Program ir= 1 BlBegin_1: 2122 BlEnd_1:34
Block
{
DeclL
StmtL ir=  BlBegin_1:   BlEnd_1: 
1
21 22
3 4
Stmt1
ir=1
;
}
StmtL ir=BlBegin_1: 2122 BlEnd_1:34
Stmt2
ir=BlBegin_1: 2122 BlEnd_1:
Block ir= 
21 22
{
DeclL
;
StmtL ir= 
3 4
Stmt3
;
ir=3
StmtL ir= 
21 22
}
StmtL ir=
4
Stmt4
;
ir=4
Stmt21 ir=
21
StmtL
; StmtL ir=
22
Stmt22
ir=22
; StmtL
14
Erweiterung der Grammatik um Schleifen
und bedingte Verzweigungen
Program
Block
DeclL
Type
VarL
StmtL
Stmt
Assign
LVal
Expr
::=
::=
::=
::=
::=
::=
::=
::=
::=
::=
While
If
Cond
::=
::=
::=
Block
{ DeclL StmtL }
Type VarL ; DeclL | 
TYPELIT
IDENT , VarL | IDENT
Stmt ; StmtL | 
Block | Assign | While | If
LVal = Expr
IDENT
Expr + Expr
|
Expr - Expr
|
Expr * Expr
|
Expr / Expr
|
( Expr )
|
IDENT
|
( Type ) Expr |
INTLIT
|
FLOATLIT
while Cond Block
if Cond then Block else Block
…
15
Ü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().
16
Ü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().
17
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:



Für T  T stehen folgende Hilfsfunktionen bereit:




array(T, n), wobei T  T ein Datentyp ist und n  ,
struct(T1 k1,…,Tn kn).
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.
18
Deklarationen eigener Datentypen
Grammatik erweitert um Deklaration eigener Datentypen:
Program
TypeDeclL
TypeDecl
NewType
::=
::=
::=
::=
newTypeL
Block
::=
::=
TypeDeclL Block
TypeDecl ";" TypeDeclL | 
IDENT = NewType
IDENT |
"array" [INTLIT] "of" NewType |
"struct" { NewTypeL }
NewType IDENT| NewType IDENT , NewTypeL
{ DeclL StmtL }
Beispiel zur Deklaration eigener Datentypen:
myint
a1int
a2int
t
=
=
=
=
int;
array [10] of int;
array [10] of array [5] of int;
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
19
Attributierte Grammatik zur Übersetzung
von Struktur- und Feldzugriffen
Stmt
Assign
LVal
Expr
::=
::=
::=
::=
While
Q
::=
::=
Block | Assign | While | If
LVal = Expr
IDENT
Expr + Expr
|
Expr - Expr
|
Expr * Expr
|
Expr / Expr
|
( Expr )
|
IDENT
|
( Type ) Expr |
INTLIT
|
FLOATLIT
|
IDENT Q
while Cond Block
[ Expr ] Q | . IDENT Q | [ Expr ] | . IDENT
20
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 id=i
t=array(anonym_2,20)
[ Expr ]
INTVAL iVal=15
.
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)
Q
t=struct(int a, a2int b)
IDENT id=b
Q
t=array(anonym_1,10)
[ Expr ]
INTVAL iVal=8.
Q
t=array(int, 5)
Q
[ Expr ]
INTVAL iVal=4.
21
Übersetzung von Feld- und
Strukturzugriffen in Ausdrücken (1)

ir[Expr,10],0 := ( iro 
"tb:=&id1" 
"tp:=tb+to" 
"t:=@tp", t), wobei







(iro,to) = ir2,
tb := getNextTemp(), tp := getNextTemp(), t := getNextTemp().
( ire 
"ts:=s" 
"to:=ts*te", to), wobei
ir[Q,3],0 :=

Q ::= [ Expr ]
ir2 = (ire,te)
t0 = array(T',n) und s = sizeof(T')
ts := getNextTemp()
to := getNextTemp()
ir[Q,4],0 :=
( "to:=off", to), wobei
i- 1

Expr ::= IDENT Q
off =
å
Q ::= . IDENT
sizeof (T, ifalls
) t0 = struct(T1 n1,…,Tm nm) und id2 = ni
k= 1

to := getNextTemp().
22
Übersetzung von Feld- und
Strukturzugriffen in Ausdrücken (2)

ir[Q,1],0 :=







ir2 = (ire,te),
ir4 = (irlo,tlo),
t0 = array(T',n) und s = sizeof(T'),
ts := getNextTemp(),
tno := getNextTemp(),
to := getNextTemp().
ir[Q,2],0 :=




(ire  irlo 
"ts:=s" 
"to:=ts*te" 
"tno:=to+tlo", tno), wobei
(irlo 
"to:=off" 
"tno:=to+tlo", tno), wobei
Q ::= [ Expr ] Q
Q ::= . IDENT Q
off = offset(ni), falls t0 = struct(T1 n1,…,Tm nm) und id2 = ni,
ir3 = (irlo,tlo),
to := getNextTemp(),
tno := getNextTemp().
23
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)
Expr
IDENT id=i
[ Expr ]
t=array(anonym_2,20)
ir=( t0:=15,t0)
INTVAL iVal=15
.
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)
Q
t=struct(int a, a2int b)
IDENT id=b
Q
t=array(anonym_1,10)
Q
[ Expr ] ir=( t1:=8,t1) t=array(int, 5) Q
INTVAL iVal=8.
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)
ir=( t2:=4
t3:=4 //sizeof(int)
t4:=t3*t2,t4)
[ Expr ]
ir=( t1:=8
t2:=4
t3:=4 //sizeof(int)
t4:=t3*t2
t5:=20 //sizof(anonym_1)
t6:=t5*t1
t7:=t6+t4,t7)
ir=( t2:=4,t2)
INTVAL iVal=4.
24
Grammatik zur Übersetzung von
Funktionsaufrufen und -deklarationen
Program
FuncL
::=
::=
FormalPram::=
Block
::=
TypeDeclL FuncL
IDENT IDENT ( ) Block |
IDENT IDENT ( FormalParam ) Block
IDENT IDENT , FormalParam | IDENT IDENT
{ DeclL StmtL }
…
Expr
::=
ParamL
While
Q
::=
::=
::=
Expr + Expr
|
Expr - Expr
|
Expr * Expr
|
Expr / Expr
|
( Expr )
|
IDENT
|
( Type ) Expr |
INTLIT
|
FLOATLIT
|
IDENT Q
|
IDENT ( ParamList ) | IDENT ( )
Expr | Expr , ParamL
while Cond Block
[ Expr ] Q | . IDENT Q | [ Expr ] | . IDENT
25
Ü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.
26
Ü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 id=f
(
ParamList pl=(t', t)
)
ir= '  
Exprir=(',t')
,
, ParamList pl=t
ir=
Expr
ir=(,t)
27
Basisblöcke

Ein Basisblock ist eine Folge maximaler Länge von Anweisungen im 3Adress-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.
28
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-CodeAnweisungen.
Kanten in E repräsentieren durch Variablen modellierte
Datenabhängigkeiten:


Kanten in A repräsentieren durch Speicherzugriffe entstehende
Datenabhängigkeiten:




Lese-Schreib-Abhängigkeit (input-dependence),
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.
29
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:
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}

Hilfsfunktionen:
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
30
Ü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
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)}
// passiert nur, wenn y Programmvariable
31
Ü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)}
32
Beispiel 1
Beispiel: a = 2*(b+a-2) * (b+a)
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
read a
3
read b
2
const 2
1
S
(t0,1,W)
(b,2,R)
+
4
5
(t2,3,W)
(t3,4,W)
*
6
*
7
write a
(t1,2,W)
(a,3,R)
8
(t4,1,W)
(t5,5,W)
(t6,6,W)
(t7,2,W)
(t8,3,W)
(t9,4,W)
(t10,7,W)
(a,7,W)
33
Ü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}
34
Beispiel 2
Beispiel: a[i] = b[i] + a[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
1
read j
7
const 4
2
*
3
const &b
4
+
5
6
*
8
+
13
load
+
10
11 load
+
12
14 store
const &a
9
S
(i,1,R)
(t0,1,W)
(t1,2,W)
(t2,3,W)
(t3,4,W)
(t4,5,W)
(t5,6,W)
(j,7,R)
(t6,7,W)
(t7,2,W)
(t8,8,W)
(t9,9,W)
(t10,10,W)
(t11,11,W)
(t12,12,W)
(t13,1,W)
(t14,2,W)
(t15,3,W)
(t16,9,W)
(t17,13,W)
35
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).
36
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.



Ein Steuerflussgraph (N,E,q,s) ist ein gerichteter Graph:





(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.
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.
37
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.
38
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
d:= a
Steuerflussgraph mit 3-Adress-Code
a2:= 2
d:= (a1,a2)
Steuerflussgraph mit SSA-Code
39
Ende der Zwischencodeerzeugung
Weiter zur Zielcodeerzeugung
Herunterladen