Generowanie kodu pośredniego Java Java Sparc Sparc ML ML MIPS MIPS Pascal Pascal IR Pentium Pentium C C Alpha Alpha C++ C++ Wykład 11 10 maja 2004 IR – intermediate representation kod pośredni
Cechy kodu pośredniego Z poprzedniej strony wynikają różne ważne wnioski m.in. - nawet gdy nasz kompilator ma tylko jeden front-end i tylko jedną maszynę docelową to warto starannie dobrać sposób reprezentowania kodu pośredniego, - kod pośredni powinien być łatwy w produkcji, - powinno się zapewnić łatwą translację na kod realnej maszyny docelowej, - każda konstrukcja powinna mieć jasne i proste znaczenie (ma to znaczenie dla procesu ulepszania kodu) Kod pośredni w.11 10 maja-2004 1
Drzewa jako reprezentacja kodu Oczywiście, można uznać, że reprezentacją kodu jest drzewo składniowe. assign | | a := b*-c + b*-c id a + | | * | | * | | id b id b uminus | | uminus | | id c id c Kod pośredni w.11 10 maja-2004 1
Drzewa jako reprezentacja kodu II Oczywiście, można uznać, że reprezentacją kodu jest drzewo składniowe. Przestaje to być zaskakujące gdy drzewo takie zapiszemy liniowo. assign | | 0 id b 1 id c 2 uminus 1 3 * 0 2 4 id b 5 id c 6 uminus 1 7 * 4 6 8 + 3 7 9 id a 10 assign 9 8 11 id a + | | * | | * | | id b id b uminus | | uminus | | id c id c Kod pośredni w.11 10 maja-2004 1
Dagi jako reprezentacja kodu Zamiast drzew możemy użyć dagów tzn directed acyclic graphs czyli grafów skierowanych acyklicznych. assign | | 0 id b 1 id c 2 uminus 1 3 * 0 2 4 + 3 3 5 id a 6 assign 9 8 7 id a + | | * | | id b uminus | | id c Kod pośredni w.11 10 maja-2004 1
“Czwórki” czyli kod trójadresowy Po udekorowaniu drzewa składniowego odpowiednimi atrybutami np. typy, offsety nazw lokalnych, apetyt na wielkości tymczasowe, ... można zapisać drzewo (lub dag) w postaci liniowej. Kod czwórkowy jest ciągiem instrukcji o jednej operacji op i trzech adresach. Ogólna postać instrukcji jest: x := y op z x, y i z są stałymi, nazwami albo wygenerowanymi przez kompilator zmiennymi tymczasowymi bądź etykietami, op oznacza operator arytmetyczny, relacyjny bądź logiczny. Kod pośredni w.11 10 maja-2004 1
Repertuar instrukcji trójadresowych instrukcje przypisania x := y op z instrukcje przypisania x := op z instrukcje kopiujące x := y skoki bezwarunkowe goto L skoki warunkowe if x oprel y goto L instrukcje sekwencji wywołujących param x i call p, n oraz return y operacje na tablicach x :=a[i] oraz a[i] := x operacje na wskaźnikach x := &y, x := *y, *x :=y ____ Jest to przykład. Zestaw operatorów i repertuar instrukcji zależy od języka źródłowego i od zbioru maszyn docelowych. Kod pośredni w.11 10 maja-2004 1
Przykład S id:=E E E1 +E2 E E1 * E2 E - E1 E (E1) E id Generowanie kodu pośredniego może odbywać się dzięki akcjom semantycznym operującym na atrybutach węzłów drzewa składniowego. Gramatyka S id:=E E E1 +E2 E E1 * E2 E - E1 E (E1) E id Atrybutami nieterminali niech będą E.pozycja i E.kod oraz S.kod. Metoda nowatymcz obiektu g klasy GeneratorTymcz zwraca w kolejnych wywołaniach nowe nazwy t1, t2, ... Kod pośredni w.11 10 maja-2004 1
Przykład c.d. Produkcja Reguła semantyczna S id:=E S.kod := E.kod ^ gen( id.pozycja ':=' E.pozycja) E E1 +E2 E.pozycja := g.nowatymcz; E.kod := E1.kod ^ E2.kod ^ gen(E.pozycja ':=' E1.pozycja '+' E2.pozycja) E E1 * E2 E.pozycja := g.nowatymcz; E.kod := E1.kod ^ E2.kod ^ gen(E.pozycja ':=' E1.pozycja '*' E2.pozycja) E - E1 E.pozycja := g.nowatymcz; E.kod := E1.kod ^ gen(E.pozycja ':=' '-' E2.pozycja) E (E1) E.pozycja := E1.pozycja; E.kod := E1.kod E id E.pozycja := id.pozycja; R.kod := ' ' Kod pośredni w.11 10 maja-2004 1
Przykład - zastosowanie Zastosowanie tych produkcji wraz z odpowiadającymi im regułami semantycznymi daje dla drzewa składniowego ze str. 3 następujący ciąg instrukcji kodu pośredniego t1 := - c t2 := b * t1 t3 := - c t4 := b * t3 t5 := t2 + t4 a := t5 Uwaga. W większości kompilatorów ten kod zapisywany jest na pliku. Kod pośredni w.11 10 maja-2004 1
Kod instrukcji sterujących Zobaczmy jak wygląda generowanie kodu pośredniego dla instrukcji while. Potrzebne nam są teraz dwa atrybuty: S.początek i S.wyjście. Produkcja Reguła semantyczna S while E do S1 S.początek := nowaEtykieta; S.wyjście := nowaEtykieta; S.kod := gen(S.początek ':') ^ E.kod ^ gen('if' E.pozycja=0 'goto' S.wyjście) ^ S1.kod ^ gen('goto' S.początek) ^ gen(S.wyjście ':') Daje to następujący obraz Kod pośredni w.11 10 maja-2004 1
Kod instrukcji sterujących II S.początek: E.kod if E.pozycja = 0 goto S.wyjście S1.kod goto S.początek S.wyjście: Kod pośredni w.11 10 maja-2004 1
Metoda genkod w obiektach-węzłach Jest oczywiste, że tworząc kod pośredni przy pomocy metod programowania obiektowego jesteśmy skłonni raczej posłużyć się metodami wirtualnymi niż wykorzystywać reguły semantyczne. Istota algorytmu pozostaje bez zmian, inaczej tylko go zapisujemy. W każdej podklasie klasy Expression deklarujemy odpowiednią metodę genkod w taki sposób, że w klasach Suma i Iloczyn pojawią się metody genkod różniące się wygenerowaną instrukcją trójadresową: gen(E.pozycja ':=' E1.pozycja '+' E2.pozycja) w węźle sumy, i gen(E.pozycja ':=' E1.pozycja '*' E2.pozycja) w węźle iloczynu. Kod pośredni w.11 10 maja-2004 1
Szkic obiektowego generowania kodu class Expression () { Expression left, right; KodPośredni kod; Adres pozycja; void genkod(){} } // Expression; class Suma extends Expression() { void genkod() { left.gencod(); right.gencod(); t = g.nowaTymczas; gen('t := left.pozycja '+' right.pozycja); } } // Suma class Iloczyn extends Expression () { // podobnie } Kod pośredni w.11 10 maja-2004 1
Oszczędne używanie nazw tymczasowych Możemy przyjąć, że szafujemy nazwami tymczasowymi bez ograniczeń, że jest ich dowolnie dużo do naszej dyspozycji. Tak postępujemy w przypadku kompilatorów optymalizujących bo ... Możliwe są dwa podejścia: najpierw wygenerować wiele nazw tymczasowych, a potem umieszczać kilka nazw tymczasowych pod jednym adresem – sprowadza się to do kolorowania pewnego grafu. Inne podejście polega na obserwacji, że wiele nazw tymczasowych jest tworzonych dla jednokrotnego wykorzystania. Przykład oblicz wartość wyrażenia E1 i zapisz w t1 oblicz wartość wyrażenia E2 i zapisz w t2 t3 := t1 + t2 Te zmienne nie będą używane w żadnym innym miejscu programu! Kod pośredni w.11 10 maja-2004 1
Przykład dyscypliny stosowej Rozpatrzmy instrukcję x := a*b+c*d-e*f Przyjmijmy, że zmienne tymczasowe mają tę samą szerokość. Utrzymujemy licznik zmiennych tymczasowych L. Za każdym razem gdy potrzebna jest nowa zmienna użyjemy nazwy $L. Za każdym razem gdy użyjemy zmiennej tymczasowej zmniejszamy licznik o jeden L := L-1. Instrukcja Wartość L 0 $0:=a*b 1 $1:=c*d 2 $0:=$0+$1 1 użyto dwu zmiennych jako argumentów $1:=e*f 2 $0:=$0-$1 1 x := $0 0 Kod pośredni w.11 10 maja-2004 1
Adresowanie elementów w tablicy W niemal każdym języku programowania elementy w tablicy zajmują kolejne miejsca w pamięci. Wynika stąd, że można szybko do nich dotrzeć element jego adres A[i] baza+(i – lower(A) * w gdzie baza – adres pamięci zarezerwowanej dla tablicy w - szerokość elementu w tablicy A, zależy od typu, lower(A) – dolne ograniczenie indeksów tablicy A UWAGA. Kompilator, który nie dodaje sprawdzenia zakresu indeksu i jest niedobrym produktem. Dlaczego? Jak zapewnić sprawdzanie zakresów indeksów? Kod pośredni w.11 10 maja-2004 1
Adresowanie tablic dwuwymiarowych W wielu kompilatorach tablice dwuwymiarowe umieszczane są w tablicy jednowymiarowej. Zachodzą wtedy dwa wypadki: tablica przechowywana jest wierszami np. Pascal, C, A[i,j] ma adres baza+((i-dółwierszy)*n2+j-dółkolumn)*w gdzie n2 = górakolumn – dółkolumn +1 baza = adres początku tablicy w pamięci, albo tablica przechowywana jest kolumnami np. Fortran. A[i,j] baza+((j-dółkolumn)*n1+i-dółwierszy)*w Zauważ, że zastosowano schemat Hoernera, co jest ważne w przypadku tablic więcej wymiarowych. Kod pośredni w.11 10 maja-2004 1
Adresowanie tablic dwuwymiarowych II W Loglanie i w Javie tablice są pewnego rodzaju obiektami. Tablice jednowymiarowe adresuje się tak samo. Tablice dwu- i więcej wymiarowe traktuje się jak tablice jednowymiarowe tablic o n-1 wymiarach. Czyli zapis A[i,j] jest skrótem dla A[i][j]. Kod pośredni w.11 10 maja-2004 1
Tworzenie kodu dla wyrażeń logicznych I Dwie możliwości: A) obliczenia “numeryczne”wartości wyrażeń boolowskich, B) wykorzystanie skoków warunkowych A) wartością wyrażenia jest albo 1(true) albo 0(false) wyrażenie a or b and not c zostaje przetłumaczone na ciąg instrukcji t1 := not c t2 := b and t1 t3 := a or t2 Wartości atomowych wyrażeń boolowskich obliczamy przy pomocy skoków warunkowych [100] if a <b goto 103 [101] t := 0 [102] goto 104 [103] t := 1 Kod pośredni w.11 10 maja-2004 1
Tworzenie kodu dla wyrażeń logicznych II Produkcja Reguła semantyczna S id:=E S.kod := E.kod ^ gen( id.pozycja ':=' E.pozycja) E E1 or E2 E.pozycja := g.nowatymcz; E.kod := E1.kod ^ E2.kod ^ gen(E.pozycja ':=' E1.pozycja 'or' E2.pozycja) E E1 and E2 E.pozycja := g.nowatymcz; E.kod := E1.kod ^ E2.kod ^ gen(E.pozycja ':=' E1.pozycja 'and' E2.pozycja) E not E1 E.pozycja := g.nowatymcz; E.kod := E1.kod ^ gen(E.pozycja ':=' '-' E2.pozycja) E (E1) E.pozycja := E1.pozycja; E.kod := E1.kod E id E.pozycja := id.pozycja; E.kod := ' ' Kod pośredni w.11 10 maja-2004 1
Tworzenie kodu dla wyrażeń logicznych III Produkcja Reguła semantyczna E id1 relop id2 E.pozycja := g.nowatymcz; E.kod := gen('if' id1.pozycja 'relop' id2.pozycja 'goto' nextstat+3)^ gen(E.pozycja:=0)^gen(goto nextstat+2)^ gen(E.pozycja:=1) E true E.pozycja := g.nowatymcz; E.kod := gen(E.pozycja ':='1' ) E false E.pozycja := g.nowatymcz; E.kod := gen(E.pozycja ':=' 0 ') Kod pośredni w.11 10 maja-2004 1
Tworzenie kodu dla wyrażeń logicznych IV Wyrażenia logiczne realizowane przez skoki warunkowe B) wartość wyrażenia jest reprezentowana przez pozycję w kodzie W poniższym przykładzie wiadomo, że gdy program osiągnie instrukcję 101-szą to wartością zmiennej t jest fałsz, a gdy osiągnie instrukcję 103-cią to wartością zmiennej t jest true. [100] if a <b goto 103 [101] t := 0 [102] goto 104 [103] t := 1 Dla obliczenia alternatywy a or b wystarczy być może obliczenie wartości a: [200] wynik := a [201] if wynik goto 203 [202] wynik := b [203] {zmienna wynik ma w tym miejscu wartość a or b} W podobny sposób można wyznaczyć warości wyrażeń a and b oraz not a. Ten sposób w literaturze ma nazwę “short circuit” Kod pośredni w.11 10 maja-2004 1
Instrukcje sterujące I Produkcja Reguły semantyczne S if E then S1 E.true := new label; E.false := S.next; S1.next := S.next; S.code := E.code^gen(E.true ':')^ S1.code S if E then S1 E.true := new label; E.false := new label; else S2 S1.next := S.next; S2.next := S.next; S.code := E.code ^ gen(E.true ':')^ S1.code^ gen(goto S.next)^ gen(E.false':') ^S2.code S while E do S1 S.begin := new label; E.true := new label; E.false := S.next; S1.next := S.begin; S.code := gen(S.begin ':')^ E.code ^ gen(E.true ':')^ S1.code ^ gen(goto S.begin) Kod pośredni w.11 10 maja-2004 1
Instrukcje sterujące II Kod instrukcji if E then S1: goto E.true E.code goto E.false E.true: S1.code E.false: Kod pośredni w.11 10 maja-2004 1
Instrukcje sterujące III Kod instrukcji while E do S1: S.begin: goto E.true E.code goto E.false E.true: S1.code goto S.begin E.false: Kod pośredni w.11 10 maja-2004 1
Instrukcje sterujące IV Kod instrukcji if E then S1 else S2: goto E.true E.code goto E.false E.true: S1.code goto S.next E.false: S2.code S.next: Kod pośredni w.11 10 maja-2004 1
Wyrażenia logiczne realizowane przez skoki warunkowe Kod pośredni w.11 10 maja-2004 1
Backpatching p Kod pośredni w.11 10 maja-2004 1
Wywołania procedur Rozważmy gramatykę instrukcji procedury S call id ( Elist) Elist Elist , E | E Jak pamiętamy z poprzedniego wykładu instrukcja procedury tłumaczy się na sekwencję wywołującą złożoną z ciągu poleceń obliczających kolejne parametry aktualne i przekazujących je instrukcją param x, oraz z instrukcji call p, n która wywołuje procedurę biblioteki wspomagania przetwarzania (run-time system, lub maszyna wirtualna). Polecenie call ma dwa argumenty: pierwszym jest informacja związana z nazwą procedury, drugim jest liczba n przekazywanych argumentów. Kod pośredni w.11 10 maja-2004 1
Wywołania procedur II Rozpatrzmy prosty przykład: instrukcja procedury postaci id(E, E, ... ,E) załóżmy, że parametry są przekazywane przez referencję trzeba więc wygenerować kod pośredni prowadzący do obliczenia tych argumentów, które nie są nazwami prostymi, a po nich umieścić instrukcje param, po jednej dla każdego argumentu. Można to zrobić posługując się kolejką. Akcja semantyczna dla Elist -> Elist będzie zawierała krok zapamiętujący atrybut E.pozycja w kolejce kolejka. Akcja semantyczna dla S -> call id (Elist) wygeneruje instrukcję param dla każdego elementu z kolejka Kod pośredni w.11 10 maja-2004 1
Wywołania procedur III Kod pośredni w.11 10 maja-2004 1
p p Kod pośredni w.11 10 maja-2004 1
p p Kod pośredni w.11 10 maja-2004 1
p p Kod pośredni w.11 10 maja-2004 1