Generowanie kodu pośredniego Java ML Pascal C C++ Alpha Pentium Sparc Java ML Pascal C C++ Alpha Pentium Sparc MIPS IR IR – intermediate representation
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
Drzewa jako reprezentacja kodu Oczywiście, można uznać, że reprezentacją kodu jest drzewo składniowe. Kod pośredni w assign | | id a + | | * | | id b uminus | | id c * | | id b uminus | | id c a := b*-c + b*-c
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. Kod pośredni w assign | | id a + | | * | | id b uminus | | id c * | | id b uminus | | id c 0 id b 1 id c 2 uminus 1 3 * id b 5 id c 6 uminus 1 7 * id a 10 assign
Dagi jako reprezentacja kodu Zamiast drzew możemy użyć dagów tzn directed acyclic graphs czyli grafów skierowanych acyklicznych. Kod pośredni w assign | | id a + | | * | | id b uminus | | id c 0 id b 1 id c 2 uminus 1 3 * id a 6 assign 9 8 7
“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
Repertuar instrukcji trójadresowych 1 instrukcje przypisania x := y op z 2 instrukcje przypisania x := op z 3 instrukcje kopiujące x := y 4 skoki bezwarunkowe goto L 5 skoki warunkowe if x oprel y goto L 6 instrukcje sekwencji wywołujących param x i call p, n oraz return y 7 operacje na tablicach x :=a[i] oraz a[i] := x 8 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
Przykład 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 E 1 +E 2 E E 1 * E 2 E - E 1 E (E 1 ) 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 t 1, t 2,... Kod pośredni w
Przykład c.d. Produkcja Reguła semantyczna S id:=E S.kod := E.kod ^ gen( id.pozycja ':=' E.pozycja E E 1 +E 2 E.pozycja := g.nowatymcz; E.kod := E 1.kod ^ E 2.kod ^ gen(E.pozycja ':=' E 1.pozycja '+' E 2.pozycja) E E 1 * E 2 E.pozycja := g.nowatymcz; E.kod := E 1.kod ^ E 2.kod ^ gen(E.pozycja ':=' E 1.pozycja '*' E 2.pozycja) E - E 1 E.pozycja := g.nowatymcz; E.kod := E 1.kod ^ gen(E.pozycja ':=' '-' E 2.pozycja) E (E 1 ) E.pozycja := E 1.pozycja; E.kod := E 1.kod E id E.pozycja := id.pozycja; R.kod := ' ' Kod pośredni w
Przykład - zastosowanie Zastosowanie tych produkcji wraz z odpowiadającymi im regułom semantycznym 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 kod taki zapisywany jest na pliku. Kod pośredni w
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 S 1 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
Kod instrukcji sterujących II Kod pośredni w S.początek: S.wyjście: E.kod if E.pozycja = 0 goto S.wyjście S 1.kod goto S.początek
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 ':=' E 1.pozycja '+' E 2.pozycja) w węźle sumy, i gen(E.pozycja ':=' E 1.pozycja '*' E 2.pozycja) w węźle iloczynu. Kod pośredni w
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
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
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
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 to zrobić? Kod pośredni w
Adresowanie tablic dwuwymiarowych W wielu kompilatorach tablice dwuwymiarowe umieszczane są w tablicy jednowymiarowej. Zachodzą wtedy dwa wypadki: tablica przechowywana jest wierszami np. Pascal, C, albo A[i,j] ma adres baza+((i-dół wierszy )*n2+j-dół kolumn )*w gdzie n2 = góra kolumn – dół kolumn +1 baza = adres początku tablicy w pamięci, 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
Adresowanie tablic dwuwymiarowych II W Loglanie i w Javie tablice są obiektami pewnego rodzaju. 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
Tworzenie kodu dla wyrażeń logicznych Kod pośredni w
Instrukcje sterujące Kod pośredni w
Wyra żeniia logiczne realizowane przez skoki warunkowe Kod pośredni w
Backpatching p Kod pośredni w
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
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
Wywołania procedur III p Kod pośredni w
p p Kod pośredni w