Vorlesung Compilertechnik Sommersemester 2009 Zielcodeerzeugung M. Schölzel Aufgabe der Zielcodeerzeugung Erzeugung von Programmcode, der auf der gewünschten Zielarchitektur ausgeführt werden kann (oft Assemblercode). Das schließt ein: Übersetzung der Zwischencodeanweisungen in Assmeblercode (Code-Selection, Scheduling). Registerallokation. Erzeugung einer Laufzeitumgebung für das Programm. Speicherorganisation. 2 Einbettung der Zielcodeerzeugung in den Compiler Programmkode enthält den aus den Zwischenkodeanweisungen erzeugten Zielcode und statisch erzeugten Zielcode (z.B. Prolog zur Initialisierung der Laufzeitumgebung, Prozeduren zur dynamischen Speicherverwaltung) Erzeugung statischer Daten (globale Variablen, Konstanten) Heap speichert dynamisch erzeugte Objekte, die in einer Prozedur erzeugt werden und auch nach dem Verlassen der Prozedur erhalten bleiben sollen. Stack speichert Rücksprungadressen und lokale Variablen von Prozeduren. Programmcode Zwischencodeerzeugung Statische Daten 3-Adress-Code t2 := t1 + t0 t3 := a t4 := t2 * t3 … Zielcodeerzeugung Heap Stack 3 Prinzipielles Vorgehen Für jede Anweisungsart des 3-Adress-Codes ist eine Schablone für den zu erzeugenden Zielcode bekannt. In dieser Schablone müssen noch die Speicherorte (i.Allg. Register), die die Werte der Variablen des 3-Adress-Codes enthalten, eingetragen werden. Die benötigten Werte werden durch die Registerallokation an geeigneten Orten zur Verfügung gestellt. Während der Zielcodeerzeugung wird in geeigneten Datenstrukturen dieser Speicherort mitprotokolliert. Registerallokation erzeugt auch den Zielcode zum Laden/Speichern von Registerwerten aus dem/in den Speicher. Registerinhalte ein-/auslagern mov [0x1450],r4 mov r7,[0x1454] … t12 := a t13 := t5 + t12 b := t13 if t11 then goto next … add r0,r4,r7 r?,r?,r? r0,r?,r? r0,r4,r? Anweisungart identifizieren und Schablone erzeugen Wert von t5 in Register bereitstellen Wert von t12 in Register bereitstellen Zielregister für t13 bereitstellen 4 Registerallokation Ziel: Abbildung der temporären Variablen eines Zwischencodeprogramms auf eine beschränkte Anzahl von Prozessorregistern. Klassifizierung der Registerallokation: Lokal: Auf den Basisblock beschränkt. Global: Für Funktionen oder das gesamte Programm. Vorgehensweise bei der Registerallokation hängt stark von der Zielarchitektur ab: Register-/Register-Architektur oder Register-/SpeicherArchitektur, 2-Adress- oder 3-Adress-Architektur, Universeller Registersatz oder Spezialregistersatz, Flache oder tiefe Registerhierarchie. 5 Fiktive Zielarchitektur Bei den weiteren Erläuterungen betrachten wir eine Zielarchitektur mit folgenden Eigenschaften: 32-Bit-Register: Operationen und Bedeutung: Stackpointer: sp Basepointer: bp Weitere allgemeine Register: r0,…,r15 mov mov mov mov add add rx,ry: ry := rx #imm, rx: rx := imm [addr],rx: rx := mem[addr] rx,[addr]: mem[addr] := rx rx,ry,rz: rz := rx + ry rx,#imm,rz: rz := rx + imm Dabei sind: rx, ry, rz {sp, bp, r0, …, r16}, imm ist ein 32-Bit-Wert, addr ist eine 32-Bit-Speicheradresse oder addr {sp, bp, r0, …, r16} oder addr = bp + imm 6 Lokale Registerallokation für 3-Adress-Register/Register-Architektur: Verwaltungsstrukturen V ist die Menge aller Variablen im 3-Adress-Code. Registerdeskriptor rd : {0,…,RegNum – 1} (V {w,r}) speichert für jedes Register die Menge der Variablen, deren Werte sich im Register befinden sowie deren Lese-/Schreibzustand. Speicherdeskriptor: sd: V speichert für jede im Speicher abgelegte Variable die Speicheradresse (absolut für globale und relativ für lokale Variablen). Belegungssituationen der Verwaltungsstrukturen: Für jede globale Variable a ist durch sd(a) immer ein Speicherplatz festgelegt. Bei Übersetzung einer Funktion f ist außerdem für jede lokale Variable a in f durch sd(a) eine relative Adresse festgelegt. Für eine temporäre Variabel existiert kein Eintrag in rd oder sd, nur ein Eintrag in rd oder nur ein Eintrag in sd oder ein Eintrag in rd und sd. Für eine Programmvariable existiert immer ein Eintrag in sd möglicherweise auch ein Eintrag in rd; dann befindet sich der aktuelle Wert der Variablen im Register. 7 Hilfsfunktionen und Schnittstelle der Registerallokation Hilfsfunktionen für eine Variable v: isLocal(v) = True gdw. der Speicherplatz für v im Stapel ist. addr(v) ist die Adresse des Speicherplatzes von v oder die relative Adresse, die während des Aufbaus der Symboltabelle festgelegt wurde. getNextFreeLocalAddress(): Liefert die nächste freie relative Adresse im Stapel getFreeReg(): liefert den Namen eines Registers, in das ein neuer Wert geschrieben werden kann. getVarInReg(v): Erzeugt den erforderlichen Zielcode, um den Wert der Variablen v in einem Register bereitzustellen. lockReg(r): Verhindert, dass der Inhalt des Registers r bei folgenden Registeranforderungen ausgelagert wird. unlockReg(r): Klar setRegDeskr(r,x): Danach gilt (x,w) = rd(r) und für alle i: 1 i RegNum und i r (x,w) rd(i) und (x,r) rd(i). delete(x,r): Danach gilt: (x,w) rd(r) und (x,r) rd(r). clear(): Löscht Einträge im Speicher- und Registerdeskriptor. saveRegs(): Sichert Register im Speicher, die aktualisierte Werte von Programmvariablen enthalten. 8 Implementierung von getFreeReg Eingabe: keine Ausgabe: Name eines Registers, dessen Inhalt überschrieben werden kann Algorithmus getFreeReg: Falls ein i existiert mit 1 i RegNum und rd(i) = dann return i Falls ein i existiert mit 1 i RegNum und für alle (v,x) rd(i) gilt x = r, dann rd(i) := , return i Wähle ein s mit 1 s RegNum und s ist nicht gesperrt Spill(s) return s Eingabe: Registernummer s Ausgabe: Zielcode zum Auslagern des Registerwertes Algorithmus Spill: for each (v,w) rd(s) do if sd(v) undefiniert then addr = getNextFreeLocalAddr() outCode("mov s,[bp-addr]") sd(v) := addr else if v ist global then outCode("mov s,[sd(v)]") else outCode("mov s,[bp-sd(v)]") fi fi od rd(s) := 9 Beispiel: getFreeReg Aufruf: getFreeReg() mit Registerdeskriptor: Registerdeskriptor: i rd(i) 0 Neuer Registerdeskriptor: i rd(i) (t0,w), (a,r) 0 (t0,w), (a,r) 1 1 2 (t2,w), (c,r) … (t15,w), (p,r) 2 (t2,w), (c,r) … (t15,w), (p,r) 15 Erzeugter Spillcode: Keiner Rückgabewert : r1 15 Aufruf: getFreeReg() mit Registerdeskriptor: Registerdeskriptor: Erzeugter Spillcode: mov r1,[bp-sd(t1)] mov r1,[sd(a)] Neuer Registerdeskriptor: i rd(i) (t0,w), (t16,w) 0 (t0,w), (t16,w) 1 (t1,w), (a,w) 1 2 (t2,w), (t18,w) … (t15,w), (t31,w) 2 (t2,w), (t18,w) … (t15,w), (t31,w) i rd(i) 0 15 Rückgabewert : r1 15 10 Implementierung von getVarInReg Eingabe: Variable t Ausgabe: Name des Registers, in dem der Wert der Variable bereitgestellt wurde Algorithmus getVarInReg: if x{r,w}i:1 i RegNum und (t,x) rd(i) then retrun i fi if i:1 i RegNum und rd(i) = then s := i else if i:1 i RegNum und (v,x) rd(i): x=r then s := i else Wähle ein Register i mit geringen Kosten beim Auslagern und i ist nicht gesperrt Spill(i) s := i fi fi rd(s) := {(t,r)} if t ist lokal then outCode("mov [bp-sd(t)],s") else outCode("mov [sd(t)],s") fi return s 11 Beispiel: getVarInReg Aufruf: getVarInReg(t1) mit Registerdeskriptor: Registerdeskriptor: i rd(i) 0 Neuer Registerdeskriptor: i rd(i) 0 1 (t1,w), (b,r) 1 (t1,w), (b,r) 2 (t2,w), (c,r) … (t15,w), (p,r) 2 (t2,w), (c,r) … (t15,w), (p,r) 15 Erzeugter Spillcode: Keiner Rückgabewert : r1 15 Aufruf: getVarInReg(t16) mit Registerdeskriptor: Registerdeskriptor: i rd(i) 0 (t0,w), (a,w) 1 (t1,w), (b,w) 2 (t2,w), (c,w) … (t15,w), (p,w) 15 Erzeugter Spillcode: mov r0,[bp-sd(t0)] mov r0,[sd(a)] mov [bp-sd(t16)],r0 Rückgabewert : r0 Neuer Registerdeskriptor: i rd(i) 0 (t16,r) 1 (t1,w), (b,w) 2 (t2,w), (c,w) … (t15,w), (p,w) 15 12 Übersetzung binärer/unärer Anweisungen Eingabe: 3-Adress-Code-Anweisung x := y z Ausgabe: Zielcode Algorithmus: l := getVarInReg(y); lockReg(l); r := getVarInReg(z); lockReg(r); if isTemp(y) then Delete(y,l); if isTemp(z) then Delete(z,r); t := getFreeReg(x); unlock(l); unlock(r); asmmnem := Assembleropertion für outCode("asmmnem l,r,t"); setRegDeskr(t,x) Eingabe: 3-Adress-Code-Anweisung x := y Ausgabe: Zielcode Algorithmus: r := getVarInReg(y); lookReg(r); if isTemp(y) then Delete(y,r); t := getFreeReg(); unlook(r); asmmnem := Assembleropertion für outCode("asmmnem r,t"); setRegDeskr(t,x) 13 Beispiel Übersetzung von t20 := t1 + t16; Aufruf von getVarInReg(t1) und getVarInReg(t16): Registerdeskriptor: i locked 0 (t0,w), (a,w) 0 1 (t1,w), (b,w) 0 2 (t2,w), (c,w) … (t15,w), (p,w) 0 15 rd(i) Erzeugter Spillcode: mov r0,[bp-sd(t0)] mov r0,[sd(a)] mov [bp-sd(t16)],r0 Rückgabewert : r1 für t1 r0 für t16 0 Neuer Registerdeskriptor: i rd(i) 0 (t16,r) 1 1 (t1,w), (b,w) 1 2 (t2,w), (c,w) … (t15,w), (p,w) 0 15 locked 0 Aufruf von getFreeReg() Registerdeskriptor: i rd(i) 0 1 1 (b,w) 1 2 (t2,w), (c,w) … (t15,w), (p,w) 0 15 locked 0 Erzeugter Spillcode: Keiner Rückgabewert : r0 Zielcode: add r1,r0,r0 Neuer Registerdeskriptor: i rd(i) 0 (t20,w) 0 1 (b,w) 0 2 (t2,w), (c,w) … (t15,w), (p,w) 0 15 locked 14 0 Übersetzung von Kopieranweisungen (1) Eingabe: 3-Adress-Code-Anweisung x := y, x := k, @x := y, x := @y oder x := &y Ausgabe: Zielcode Algorithmus: Für x := y: if ein i und ein k {r,w} ex. mit (y,k) rd(i) then rd(i) := rd(i) {(x,w)}; else i := getVarInReg(y) rd(i) := rd(i) {(x,w)}; fi if isTemp(y) then Delete(y,i) Für x := k: r := getFreeReg() outCode("mov #k,r") setRegDesk(r,x); return; Für x := &y: r := getFreeReg() if isLocal(y) then outCode("mov bp,r"); outCode("add r,#addr(y),y"); else outCode("mov #addr(y),r") setRegDesk(r,x); return; 15 Beispiel Übersetzung von t20 := z Registerdeskriptor: i locked Neuer Registerdeskriptor: i rd(i) locked 0 (t0,w), (a,w) 0 0 (t0,w), (a,w) 0 1 (z,w), (b,w) 0 1 (z,w), (b,w), (t20,w) 0 2 (t2,w), (c,w) … (t15,w), (p,w) 0 2 0 0 15 (t2,w), (c,w) … (t15,w), (p,w) 15 rd(i) Erzeugter Spillcode: keiner 0 Übersetzung von t16 := t1 Registerdeskriptor: i rd(i) 0 0 1 (b,w) 0 2 (t2,w), (c,w) … (t15,w), (p,w) 0 15 locked 0 Erzeugter Spillcode: mov [bp-sd(t1)],r0 Rückgabewert : r0 Zwischencode : Keiner Neuer Registerdeskriptor: i rd(i) 0 (t16,w) 0 1 (b,w) 0 2 (t2,w), (c,w) … (t15,w), (p,w) 0 15 locked 0 16 Übersetzung von Kopieranweisungen (2) Für @x := y: l := getVarInReg(x); lockReg(l); r := getVarInReg(y); unlock(l); if(isTemp(y) then Delete(y,r); if(isTemp(x) then Delete(x,l); outCode("mov r,[l]"); Für x := @y: r := getVarInReg(y); lockReg(r); l := getFreeReg() unlock(r); if(isTemp(y) then Delete(y,r); rd(l) := rd(l) {(x,w)} outCode("mov [r],l"); 17 Übersetzung von Labels und Sprunganweisungen Eingabe: 3-Adress-Code-Anweisung label: Ausgabe: Zielcode Algorithmus: SaveRegs(); outCode("label:"); Clear(); Aktualisieren der Werte im Speicher. … a := t7 label: t8 := a … Eingabe: 3-Adress-Code-Anweisung goto label Ausgabe: Zielcode Algorithmus: … SaveRegs(); outCode("jmp label"); a := t7 goto label10 label9: … Eingabe: 3-Adress-Code-Anweisung if x then goto l Ausgabe: Zielcode Algorithmus: t := getVarInReg(x); Delete(x,t) SaveRegs(); outCode("BNZ t,l"); Einsprung von verschiedenen Position möglich; Belegung der Register unklar. Sprung zu einer Position an der der Registerdeskriptor gelöscht wird; Aktualisieren der Wert eim Speicher nötig. Hier wird die Registerallokation fortgesetzt. Sprung zu einer Position an der der Registerdeskriptor gelöscht wird; Aktualisieren der Wert eim Speicher nötig. … a := t7 if t8 then goto label20 b := t9 Fortsetzung der Registeralloka-tion. Belegung … der Register für jede Programmausführung fest. 18 Aktivierungsblock Temporäre Daten Lokale Daten Ausgelagerte Registerwerte im aktuellen Block Lokale Variablen im aktuellen Block … Aktivierungsblock der aufgerufenen Funktion Die Aktivierung einer Funktion durch einen Aufruf erfordert im Allgemeinen die Erzeugung eines Aktivierungsblocks im Laufzeitstapel. Möglicher Aufbau eines Aktivierungsblocks: Lokale Daten Maschinenregister Zugriffsverweis Rücksprungadresse Rückgabewerte Aktuelle Parameter Lokale Variablen in Blöcken, die den aktuellen Block enthalten Gesicherter Maschinenstatus der rufenden Funktion (z.B. Registerinhalte, Statusregister) Verweis auf den Aktivierungsblock der rufenden Funktion Adresse des rufenden call-Befehls Rückgabewert, falls vorhanden (kann sich aber auch in einem Register befinden) Aktuelle Parameter der aufgerufenen Funktion Aktivierungsblock der rufenden Funktion 19 Übersetzung eines Funktionsaufrufs Eingabe: 3-Adress-Code-Anweisung x := call f(t1,..,tn) Ausgabe: Zielcode Algorithmus: for i := n downto 1 do p := getVarInReg(ti) outCode("push p") Delete(p,ti) od // SaveRegs() erforderlich, falls call einen Basisblock abschließt outCode("add sp,#4,sp"); outCode("call f"); // Clear() erforderlich, falls call einen Basisblock abschließt r := getFreeReg() outCode("pop r") // Ergebniswert laden rd(r) := rd(r) {(x,w)} // vorausgesetzt Stack wächst zu kleineren Adressen outCode("add sp,#4*n,sp"); int g { … x = f(3,4); … } t0 t1 t2 x … := := := := 3 4 call f(t0,t1) t2 push r3 push r2 add sp,#4,sp call f pop r1 add sp,#8,sp sp sp Zwischencode Zielcode undefiniert 3 4 sp bp Quelltext Rücksprungadresse Aktivierungsblock der Funktion g 20 Laufzeitstapel Übersetzung einer Funktionsdeklaration Eingabe: 3-Adress-Code-Anweisung Function Label: Ausgabe: Zielcode Algorithmus: outCode("push bp") outCode("mov sp,bp"); // aktuelle Parameter sind über positive Offsets // größer gleich 12 erreichbar // lokale Variablen mit negativen Offsets outCode("add sp,#frameSize,sp") int f(int a, int b) { … } Quelltext … Function f: … push bp mov sp,bp add sp,#16,sp sp Lokale Variablen sp bp sp Zwischencode Zielcode bp der rufenden Fkt. Rücksprungadresse undefiniert 4 (Parameter a) 3 (Parameter b) Aktivierungsblock der Funktion g bp 21 Laufzeitstapel Übersetzung einer return-Anweisung Eingabe: 3-Adress-Code-Anweisung return x: Ausgabe: Zielcode Algorithmus: r := getVarInReg(x) outCode("mov r,[bp+8]"); SaveRegs(); outCode("mov bp,sp"); outCode("pop bp"); outCode("return"); int f(int a, int b) { … return 15; } Quelltext … return t20 … Zwischencode mov r5,[bp+8] mov bp,sp pop bp return sp Lokale Variablen sp bp sp Zielcode bp der rufenden Fkt. Rücksprungadresse undefiniert 15 3 (Parameter a) 4 (Parameter b) Aktivierungsblock der Funktion g bp 22 Laufzeitstapel Ende der Zielcodeerzeugung Weiter zur Optimierung