Co to jest kompilator?
Program który tłumaczy programy w jezyku wyzszego poziomu na kod
maszynowy procesora (np 80x86, Sparc) lub maszyny wirtualnej (np.
JVM).
Róznice miedzy interpreterem a kompilatorem:
interpreter wykonuje program,
kompilator nie wykonuje programu, a tylko tłumaczy go;
stworzenie interpretera nie wymaga znajomosci maszyny
docelowej,
stworzenie kompilatora wymaga dogłebnej znajomosci maszyny
docelowej.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 1 pazdziernika 2012 2 / 36
Co robi kompilator?
Wczytuje program, zwykle jako tekst.
Sprawdza poprawnosc i dokonuje analizy programu.
“Mysli” chwile (dokonuje szeregu transformacji programu).
Generuje kod wynikowy (synteza).
Czesci kompilatora realizujace analize i synteze okresla sie czasem
nazwami front-end i back-end
Istnieje wiele podobnych klas problemów/programów, gdzie
analizujemy dane wejsciowe (zwykle tekst)
tłumaczymy na inny “jezyk”.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 1 pazdziernika 2012 3 / 36
Analiza
Fazy analizy
analiza leksykalna — podział na leksemy (“słowa”);
analiza składniowa — rozbiór struktury programu i jej
reprezentacja w postaci drzewa;
analiza semantyczna — powiazanie uzycia identyfikatorów z
odpowiednimi deklaracjami; kontrola typów.
Kazda z faz analizy powinna dawac czytelne komunikaty o
napotkanych błedach
Trudne, ale bardzo wazne.
Faza analizy jest niezalezna od jezyka docelowego.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 1 pazdziernika 2012 4 / 36
Analiza semantyczna
Analiza deklaracji
Zapis informacji w tablicy symboli
Kontrola poprawnosci uzycia symboli i powiazanie z odpowiednimi
deklaracjami (poprzez tablice symboli).
Kontrola (lub rekonstrukcja) typów.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 1 pazdziernika 2012 9 / 36
Maszyny docelowe
Fizyczna architektura procesora, np x86, x86_64, SPARC, ARM
Maszyna wirtualna◮ stosowa, np. JVM◮ rejestrowa, np. LLVM
Maszyna wirtualna moze byc uzyta jako etap posredni na drodzedo kodu maszynowego
◮ Ahead of Time (AOT) — generacja kodu maszynowego przed
rozpoczeciem wykonania programu (np. LLVM);◮ Just in Time (JIT) — generacja kodu w trakcie wykonania, dla
wybranych fragmentów programu (np. JVM).
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 1 pazdziernika 2012 10 / 36
Synteza
Transformacja drzewa struktury do postaci dogodnej do dalszych
przekształcen (kod posredni)
Planowanie struktur czasu wykonania (rekordy aktywacji, etc.)
Ulepszanie kodu (“optymalizacja”)
Wybór instrukcji
Alokacja rejestrów (dla maszyn rejestrowych)
Generacja kodu
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 1 pazdziernika 2012 11 / 36
Generatory analizatorów leksykalnych
Pisanie analizatora leksykalnego jest zwykle zmudne.
Dlatego przewaznie jest on generowany automatycznie przez
narzedzia takie jak Flex (C,C++), JLex (Java), Alex (Haskell),
Ocamllex, C#Lex,. . .
Narzedzia takie generuja program realizujacy automat
rozpoznajacy leksemy na podstawie opisu złozonego z wyrazen
regularnych i przypisanych im akcji.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 1 pazdziernika 2012 22 / 36
Analiza syntaktyczna
Analizator syntaktyczny (parser) jest funkcja sprawdzajaca, czy
dane słowo nalezy do jezyka i, jesli tak, budujaca drzewo struktury.
Algorytm Youngera: O(n3) i nie daje drzewa struktury.
Istnieja efektywne algorytmy dla pewnych klas gramatyk.
Dwa zasadnicze podejscia:
Top-down: próbujemy sparsowac okreslona konstrukcje
(nieterminal); drzewo struktury budowane od korzenia do lisci.
Bottom-up: W danym napisie znajdujemy mozliwe konstrukcje;
drzewo budowane od lisci do korzenia ze znalezionych kawałków.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 1 pazdziernika 2012 23 / 36
Analiza top-down
Schemat analizy top-down mozem zapisac jako automat:
Jeden stan, alfabet stosowy Γ = N ∪ T , akceptacja pustym
stosem.
Na szczycie stosu a ∈ T :◮ jesli na wejsciu a — zdejmij ze stosu, wczytaj nastepny symbol.◮ wpp — bład: oczekiwano a.
Na szczycie stosu A ∈ N, na wejsciu a:◮ wybieramy produkcje A → α taka, ze a ∈ SELECT (A → α)◮ na stosie zastepujemy A przez α
Powyzszy automat oglada jeden symbol z wejscia, ale łatwo go
uogólnic na wieksza ich liczbe — automat LL(k).
Dla automatu deterministycznego, wybór produkcji jest wazny; zbiór
symboli dla których wybieramy produkcje A → α nazywamy
SELECT (A → α). Teraz zajmiemy sie obliczaniem tego zbioru.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 1 pazdziernika 2012 24 / 36
Zbiory FIRST
Notacja
Niech w ∈ T ∗
k : w =
{
a1a2 . . . ak , jesli w = a1a2 . . . akv
w#, jesli |w | < k.
(pierwszych k znaków słowa w)
Definicja (FIRST )
Niech w ∈ (T ∪ N)∗.
FIRST k (w) = {α : ∃β ∈ T ∗, w →∗ β, α = k : β}
(pierwsze k znaków słów wyprowadzalnych z w).
FIRST (w) = FIRST 1(w)
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 1 pazdziernika 2012 25 / 36
Zbiory FOLLOW
Definicja (FOLLOW )
Niech w ∈ N
FOLLOW k (w) = {α : ∃β ∈ T ∗, S →∗ µwβ, α = k : β}
(pierwsze k znaków mogacych wystapic za w).
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 1 pazdziernika 2012 26 / 36
Gramatyki LL(k)
Czytajac od Lewej, Lewostronny wywód, widzimy (k) symboli.
Definicja
Gramatyka jest LL(k), jesli dla kazdych lewostronnych wyprowadzen
S →∗ wAα → wβα →∗ wx
S →∗ wAα → wγα →∗ wy
takich, ze FIRST k (x) = FIRST k (y), mamy β = γ
“Jezeli pierwszych k symboli wyprowadzalnych z A jest wyznaczone
jednoznacznie, to takze jednoznaczne jest, która produkcja dla A musi
byc zastosowana w wyprowadzeniu lewostronnym.”
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 1 pazdziernika 2012 27 / 36
Gramatyki silnie LL(k)
Definicja
SELECT k (A → α) = FIRST k (α · FOLLOW k (A))
SELECT (A → α) = SELECT 1(A → α)
Definicja
Gramatyka jest silnie LL(k), jesli dla kazdej pary (róznych) produkcji
A → α, A → β ich zbiory SELECTk sa rozłaczne.
Dla gramatyk silnie LL(k) łatwo zbudowac parser top-down.
Kazda gramatyka silnie LL(k) jest tez LL(k).
Kazda gramatyka LL(1) jest silnie LL(1).
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 1 pazdziernika 2012 28 / 36
Lewostronna faktoryzacja
Problem pierwszego rodzaju mozemy rozwiazac “wyłaczajac przed
nawias” wspólne poczatki produkcji:
A → αβ | αγ
zastepujemy przez
A → αZ
Z → β | γ
gdzie Z jest swiezym nieterminalem.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 1 pazdziernika 2012 30 / 36
Eliminacja lewostronnej rekursjiZbiór produkcji
A → Aα | β
zastepujemy
A → βR
R → αR | ε
Na przykład, dla gramatyki
E → E + T | T
otrzymujemy gramatyke
E → TR
R → +TR | ε
lub, prosciej
E → T + E | T
Niestety, teraz ’+’ łaczy w prawo, a nie jak chcielismy — w lewo.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 1 pazdziernika 2012 31 / 36
Wyliczanie FIRST
Dla t ∈ T mamy FIRST (t) = {t}.
Dla A ∈ N mamy:
A → α1 | . . . | αn ⇒ FIRST (A) ⊇ FIRST (α1) ∪ . . . ∪ FIRST (αn)Dla A → X1 . . .Xn
FIRST (A) ⊇ FIRST (X1) \ {#}
X1 →∗ ε ⇒ FIRST (A) ⊇ FIRST (X2) \ {#}
X1X2 →∗ ε ⇒ FIRST (A) ⊇ FIRST (X3) \ {#}
. . .
X1X2 . . .Xn →∗ ε ⇒ # ∈ FIRST (A)
Prosty algorytm: zgodnie z powyzszymi regułami powiekszamy zbiory
FIRST tak długo, jak którys ze zbiorów sie powieksza (obliczamy
najmniejszy punkt stały).
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 1 pazdziernika 2012 32 / 36
Wyliczanie FOLLOW
Dla kazdych A,X ∈ N, a ∈ T , α, β ∈ (N ∪ T )∗ mamy:
A → αXaβ ∈ P , to a ∈ FOLLOW (X ).
A → αX ∈ P, to FOLLOW (A) ⊆ FOLLOW (X )
A → αXβ ∈ P, to FIRST (β) \ {#} ⊆ FOLLOW (X )
A → αXβ ∈ P, β →∗ ε to FOLLOW (A) ⊆ FOLLOW (X )
# ∈ FOLLOW (S) dla symbolu startowego S.
Prosty algorytm: zgodnie z powyzszymi regułami powiekszamy zbiory
FOLLOW tak długo, jak którys ze zbiorów sie powieksza (obliczamy
najmniejszy punkt stały).
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 1 pazdziernika 2012 34 / 36
Metoda zejsc rekurencyjnych
Metoda tworzenia parsera top-down jako zbioru wzajemnie
rekurencyjnych funkcji:
1 przekształcamy gramatyke do postaci LL(1)
2 liczymy zbiory SELECT
3 (wersja ortodoksyjna)
dla kazdego nieterminala A piszemy osobna, rekurencyjna funkcje
A.
Funkcja A rozpoznaje najdłuzszy ciag terminali (leksemów)
wyprowadzalny z A.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 8 pazdziernika 2012 10 / 30
Wersja pragmatyczna
Zakładajac, ze mamy juz gramatyke LL(1) i policzone zbiory SELECT:
1 dla kazdego nieterminala tworzymy graf składniowy; rozgałezienie
odpowiada wyborowi produkcji, zatem zbiory SELECT słuza
wyborowi drogi.
2 Sklejamy grafy, aby zmniejszyc ich liczbe, a przez to i liczbe
wywołan funkcji.
3 Zastepujemy rekursje ogonowa przez iteracje.
4 Dla kazdego grafu piszemy funkcje; graf jest schematem
blokowym takiej funkcji i wystarczy go starannie zakodowac.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 8 pazdziernika 2012 15 / 30
Przykład
Gramatyka:
E → TR R → +TR | ε
Grafy składniowe:
E
T R
R
+✎✍
☞✌T R☞
✍
✎
✌
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 8 pazdziernika 2012 16 / 30
Po połaczeniu grafów i zastapieniu rekursji ogonowej iteracja:
E
T✎
✍ +✎✍
☞✌
☞
✌
for(int stop=0;!stop;) {
T();
if(lexem==PLUS)
nextLexem();
else
stop=1;
}
}
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 8 pazdziernika 2012 17 / 30
Jak kontynuowac analize
1 Znalezc mozliwie małe poddrzewo zawierajace bład.
2 Pominac leksemy az do konca tego poddrzewa (czyli do
napotkania leksemu ze zbioru FOLLOW.
Na przykład:
void F() { // F -> (E) | num
switch(lexem) {
case lewias:
next(); E(); expect(prawias); break;
case num: next(); break;
default:
bład(...);
do {next();} while(!inFollowF(lexem));
}
}
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 8 pazdziernika 2012 19 / 30
Budowa drzewa struktury a transformacje LL(1)
Faktoryzacja gramatyki:
E → T + E | T
daje w wyniku:
E → TR
R → +E | ε
Jak zbudowac drzewo dla R?
Jakiego w ogóle typu ma byc funkcja dla R?
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 8 pazdziernika 2012 21 / 30
Kontynuacje na pomoc
Mozemy zauwazyc, ze R jest kontynuacja T. Argumentem dla R
bedzie wezeł zbudowany przez T:
Exp E() {
Exp e = T();
return R(e);
}
Exp R(Exp e){
switch(lexem) {
case PLUS: return BinOp(’+’,e, E());
case ...: return e;
...
}
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 8 pazdziernika 2012 22 / 30
Eliminacja lewostronnej rekursji
E → E + T | T
staje sie
E → TR
R → +TR | ε
Czyli podobnie jak w poprzednim przypadku. Musimy tylko zadbac o
zachowanie wiazania w lewo przy kodowaniu drugiej reguły:
Exp R(Exp e){
switch(lexem) {
case PLUS: return R(BinOp(’+’,e, T());
case ...: return e;
...
}
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 8 pazdziernika 2012 23 / 30
Budowa drzewa w wersji “pragmatycznej”
E
T✎
✍ +✎✍
☞✌
☞
✌
Exp E() {
Exp e = T();
while(lexem==PLUS) {
nextLexem();
e = BinOp(’+’,e,T());
}
return e;
}
Procedury dla operatorów wiazacych w prawo pozostawiamy w wersji
rekurencyjnej (czyli tak jak w wersji “ortodoksyjnej”).
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 8 pazdziernika 2012 24 / 30
Jak rozpoznac uchwyt?
Zbudujemy automat skonczony rozpoznajacy wiele wzorców (mozliwe
prawe strony produkcji)
Sytuacja LR(0)
A → α • β
czyli produkcja z wyróznionym miejscem.
Jestesmy w trakcie rozpoznawania A → αβ,
na stosie jest juz α, trzeba jeszcze rozpoznac β Sytuacja A → α •oznacza, ze na stosie mamy cała prawa strone produkcji i mozemy
redukowac (w metodzie SLR(1) tylko gdy na wejsciu mamy
a ∈ FOLLOW(A)).
Marcin Benke (MIM UW) MRJP 4/ 22 18 pazdziernika 2012 4 / 22
Działanie automatu LR
Dwie tablice indeksowane stanami i symbolami: ACTION (dla
terminali) i GOTO (dla nieterminali)
Stos zawiera stany przetykane symbolami gramatyki
Automat startuje ze stosem zawierajacym stan poczatkowy (z
sytuacja Z → • S#)
Niech na stosie stan s, na wejsciu terminal a:◮ ACTION[s, a] = shift p
przenosi a z wejscia na stos i nakrywa stanem p◮ ACTION[s, a] = reduce(A → α)
zdejmuje |α| par ze stosu
odsłoni sie stan q (zawierał sytuacje . . . • A . . .)
wkłada na stos A, GOTO[q,A].◮ Specjalne akcje: error, accept
Marcin Benke (MIM UW) MRJP 5/ 22 18 pazdziernika 2012 5 / 22
Konstrukcja automatu LR
1 Rozszerzamy gramatyke o produkcje Z → S# (nowy symbol
poczatkowy)2 Budujemy automat skonczony:
◮ stanami sa zbiory sytuacji◮ stan poczatkowy: Closure({Z → • S#})◮ dla stanu p przejscie po symbolu X do stanu
δ(p,X ) = Closure({A → αX • γ : A → α • Xγ ∈ p})
◮ stanem akceptujacym jest {Z → S# • }
3 Wypełniamy tablice sterujaca automatu ze stosem.
Przykład na tablicy
Marcin Benke (MIM UW) MRJP 6/ 22 18 pazdziernika 2012 6 / 22
Wypełnianie tablic sterujacych
Numerujemy stany, numerujemy produkcje.
Jednolicie dla wszystkich klas automatów wpisujemy akcje shift
(przepisujemy przejscia automatu skonczonego) i accept:
Dla przejsciap q
X
wpisujemy:
jesli X jest terminalem to
ACTION[p, x ] = shift q
jesli X jest nieterminalem to
GOTO[p, x ] = q
Jesli stan p zawiera S′ → S •#, to ACTION[p,#] = accept
Marcin Benke (MIM UW) MRJP 7/ 22 18 pazdziernika 2012 7 / 22
Redukcje
Tu postepujemy róznie dla róznych klas automatów.
Jesli stan p zawiera A → α • , to:
LR(0) wpisujemy reduce(A → α) do ACTION[p, a] dla
wszystkich a
SLR(1) wpisujemy reduce(A → α) do ACTION[p, a] dla
a ∈ FOLLOW(A)
Miejsca nie wypełnione oznaczaja error.
Jesli gdzies zostanie wpisana wiecej niz jedna akcja, to zle: gramatyka
nie jest odpowiedniej klasy (konflikt shift-reduce lub reduce-reduce).
Przykład na tablicy
Marcin Benke (MIM UW) MRJP 8/ 22 18 pazdziernika 2012 8 / 22
Sytuacje LR(1)
Sytuacja LR(1)
[A → α • β, a]
czyli para zawierajaca sytuacje LR(0) i terminal.
Jestesmy w trakcie rozpoznawania A → αβ,
na stosie jest juz α, trzeba jeszcze rozpoznac β.
Ponadto istnieje wyprowadzenie prawostronne postaci
S →∗ µAaw → µαβaw → . . .
takie, ze µα prowadzi do biezacego stanu (q0µα
−−→ q).
Sytuacja [A → α • , a] oznacza, ze na stosie mamy cała prawa strone
produkcji; mozemy redukowac gdy na wejsciu jest a.
Marcin Benke (MIM UW) MRJP 11/ 22 18 pazdziernika 2012 11 / 22
Stany i przejscia automatu LR(1)
Stanami automatu sa zbiory sytuacji LR(1).
Jesli jestesmy w sytuacji [B → α • Aβ, a], to w wyprowadzeniu po
A moze wystapic symbol z FIRST(βa). Jestesmy zatem tez w
sytuacji [A → •γ, b] dla kazdego A → γ ∈ P oraz b ∈ FIRST(βa).
Stan musi byc domkniety zwn te implikacje:
Closure(Q) – najmniejszy zbiór zawierajacy Q oraz taki, ze jesli
[B → α • Aβ, a] ∈ Closure(Q),to
∀A → γ ∈ P, b ∈ FIRST(βa) [A → • γ, b] ∈ Closure(Q)
Jesli [A → α • Xγ, a] ∈ Q dla pewnego X ∈ N ∪ T , to ze stanu Q
jest przejscie (po X ) do stanu Closure ({[A → αX • γ, a]}).
Marcin Benke (MIM UW) MRJP 14/ 22 18 pazdziernika 2012 14 / 22
Konstrukcja automatu LALR(1)
Budujemy automat ze zbiorów sytuacji LR(1).
Sklejamy równowazne stany (sumujemy stany majace identyczne
jadra).
Dalej postepujemy jak w metodzie LR(1).
Jesli nie powstana nowe konflikty, to gramatyka jest LALR(1).
Zauwazmy, ze:
Wzgledem LR(1) moga powstac tylko konflikty reduce-reduce, bo
gdyby był konflikt shift-reduce, to istniałby i przy metodzie LR(1).
automat LALR(1) ma tyle samo stanów co w metodzie LR(0)
Przykład na tablicy
Marcin Benke (MIM UW) MRJP 20/ 22 18 pazdziernika 2012 20 / 22
Zaleznosci miedzy klasami gramatyk
Pomiedzy klasami gramatyk zachodza inkluzje
LR(0) ⊂ SLR(1) ⊂ LALR(1) ⊂ LR(1)
Wszystkie powyzsze inkluzje sa ostre.
Ponadto
LL(1) ⊂ LR(1)
Uwaga: istnieja gramatyki LL(1), które nie sa LALR(1), czyli
LL(1) 6⊂ LALR(1)
Marcin Benke (MIM UW) MRJP 22/ 22 18 pazdziernika 2012 22 / 22
Analiza semantyczna
Analiza nazw◮ Czy x jest zadeklarowane przed uzyciem?◮ Która deklaracja x obowiazuje w danym miejscu programu?◮ Czy jakies nazwy sa zadeklarowane a nie uzywane?
Analiza zgodnosci typów◮ Czy wyrazenie e jest poprawne typowo?◮ Jakiego typu jest e?◮ Czy funkcja zawsze zwraca wartosc typu zgodnego z
zadeklarowanym?
Identyfikacja operacji◮ Jaka operacje reprezentuje + wyrazeniu a + b?
Odpowiedzi na te pytania moga wymagac informacji nielokalnych —
kontekstowych. Nie sa to własnosci bezkontekstowe.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 25 pazdziernika 2011 2 / 37
Gramatyki atrybutywne
Wygodnym narzedziem opisu reguł kontekstowych sa gramatyki
atrybutywne
Gramatyka atrybutywna
AG = 〈G,A,R〉
G — gramatyka bezkontekstowa, A — zbiór atrybutów, R — zbiór
reguł atrybutowania
Niech A(X) — zbiór atrybutów symbolu X;
X.a oznacza atrybut a symbolu X.
Dla produkcji p : X0 → X1 . . .Xn definiujemy reguły atrybutowania
R(p) = {Xi .a← fi,a(Xj .b . . .Xk .c) | 0 ≤ i ≤ n, a ∈ A(Xi)}
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 25 pazdziernika 2011 3 / 37
Well defined Attribute Grammar
Majac drzewo struktury chcemy dla kazdego wierzchołka X wyznaczyc
wartosci wszystkich atrubutów zgodnie z regułami atrybutowania.
Definicja (WAG)
Gramatyka atrybutywna jest dobrze zdefiniowana jesli dla kazdego
drzewa struktury zgodnego z ta gramatyka mozna w sposób
jednoznaczny wyznaczyc wartosci wszystkich atrybutów.
Niewazne “jak”, wazne, ze “mozna”.
Zagrozenia: brak reguły, sprzeczne reguły, cykl
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 25 pazdziernika 2011 4 / 37
Atrybuty syntetyzowane i dziedziczone
Dla kazdej produkcji p : X0 → X1 . . .Xn zbiorem definiujacych
wystapien atrybutów jest
AF (p) = {Xi .a | Xi .a← f (· · · ) ∈ R(p)}
Atrybut X .a jest syntetyzowany, jesli istnieje produkcja p : X → α
i X .a ∈ AF (p) (czyli zalezy od poddrzewa)
Atrybut X .a jest dziedziczony, jesli istnieje produkcja
q : Y → αXβ i X .a ∈ AF (q) (czyli zalezy od otoczenia)
Oznaczenia:
AS(X ) — atrybuty syntetyzowane X ,
AI(X ) — atrybuty dziedziczone X .
Dla symboli terminalnych mówimy o atrybutach wbudowanych —
dane przez lekser.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 25 pazdziernika 2011 5 / 37
Przykład — atrybut syntetyzowany
Konwencja: jesli dany symbol wystepuje wiecej niz raz w danej
produkcji, jego wystapienia numerujemy.
Atrybuty: E .val , T .val , F .val — syntetyzowane, num.val —
wbudowany
E0 → E1 + T {E0.val ← E1.val + T .val}E → T {E .val ← T .val}T → T ∗ F{T0.val ← T1.val ∗ F .val}T → F {T .val ← F .val}F → num {F .val ← num.val}F → (E) {F .val ← E .val}
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 25 pazdziernika 2011 6 / 37
Przykład — atrybut dziedziczony
D → TL {L.typ ← D.typ; D.typ ← T .typ}T → int {T .typ ← int}T → real {T .typ ← real}L0 → L1, id {L1.typ ← L0.typ, id .typ ← L0.typ} L→ id {id .typ ← L.typ}
Atrybuty:
T .typ, D.typ — syntetyzowany
L.typ — dziedziczony
id .typ — dziedziczony
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 25 pazdziernika 2011 7 / 37
Gramatyki zupełne
Gramatyka jest zupełna, jesli dla kazdego symbolu X spełnione sa
warunki:
1 dla kazdej produkcji p : X → α mamy AS(X ) ⊆ AF (p),
2 dla kazdej produkcji q : Y → αXβ mamy AI(X ) ⊆ AF (q),
3 AS(X ) ∪ AI(X ) = A(X ),
4 AS(X ) ∩ AI(X ) = ∅.
Mozliwa implementacja:
wierzchołki drzewa struktury — obiekty odp. klas
atrybuty syntetyzowane — metody wirtualne
atrubuty dziedziczone — przekazywane jako argumenty tychze
metod,
Atrybuty mozna przechowywac takze jako atrybuty wierzchołków, by
uniknac wielokrotnego ich wyliczania.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 25 pazdziernika 2011 8 / 37
Łatwe klasy gramatyk dobrze zdefiniowanych
Gramatyka S-atrybutowana:
wszystkie atrybuty sa syntetyzowane
wyliczanie atrybutów od lisci do korzenia — dobrze łaczy sie z
analiza wstepujaca
Gramatyka L-atrybutowana:
atrybuty moga byc syntetyzowane badz dziedziczone
atrybuty dziedziczone zaleza tylko od rodzica i rodzenstwa na
lewo
mozna wyliczyc przechodzac drzewo struktury DFS
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 25 pazdziernika 2011 10 / 37
Generalized LR
Metoda oryginalnie wymyslona przez Tomite dla analizy jezyków
naturalnych.
Budowa automatu LR dla gramatyki niejednoznacznej prowadzi
do konfliktów (kilka mozliwych akcji w jednej sytuacji).
Kazdy element tablicy automatu GLR moze zawierac zbiór akcji.
Jesli w danym momencie mamy zbiór akcji (> 1), automat
rozmnaza sie na odpowiednia liczbe kopii.
Przy napotkaniu błedu kopia ginie
Efekt: zbiór mozliwych rozbiorów danego tekstu.
Niektóre generatory (Bison,Happy) potrafia generowac parsery
GLR.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 25 pazdziernika 2011 26 / 37
Tablica symboli
Opis wszystkich bytów (zmienych, funkcji, typów, klas, atrybutów,
metod,. . . ) wystepujacych w programie.
Musi miec narzucona strukture (mechanizm wyszukiwania),
odzwierciedlajaca reguły wiazania identyfikatorów w danym
jezyku.
Opis bytu:◮ rodzaj definicji◮ inne informacje zalezne od rodzaju
Byty moga byc wzajemnie powiazane.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 25 pazdziernika 2011 27 / 37
Zasieg i zakres
Zasieg definicji identyfikatora to obszar programu, w którym mozemy
uzyc identyfikatora w zdefiniowanym znaczeniu. Nie musi byc ciagły.
Zakres to konstrukcja składniowa, z która moga byc zwiazane
definicje identyfikatorów (funkcja, blok, itp.)
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 25 pazdziernika 2011 29 / 37
Przykład
void f() {
int a;
a = g();
{
string a;
b = a;
}
h(a,b);
}
Zasieg deklaracji int a jest zaznaczony na czerwono. Jest ona
zwiazana z zakresem funkcji f.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 25 pazdziernika 2011 30 / 37
Drzewo zagniezdzen
Problem: analizujemy wezeł drzewa struktury, np przypisanie d:=e+1.
Gdzie sa definicje d i e?
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 25 pazdziernika 2011 32 / 37
Drzewo zagniezdzen
Problem: analizujemy wezeł drzewa struktury, np przypisanie d:=e+1.
Gdzie sa definicje d i e?
M ad
P abe
Q acd
R abd
T acd
S abc
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 25 pazdziernika 2011 32 / 37
Metoda I: stos tablic symboli
Wyszukiwanie:
przeszukaj zakresy od biezacego do znalezienia lub do konca,
jezeli nie znaleziono, to dodaj fikcyjna definicje dla unikniecia
kaskady błedów.
Wejscie do zakresu:
połóz na stos nowa tablice symboli,
umiesc w niej definicje zwiazane z tym zakresem
Wyjscie z zakresu:
zdejmij ze stosu ostatnia tablice symboli
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 25 pazdziernika 2011 33 / 37
Metoda II: tablica stosów
Dla kazdego identyfikatora tworzymy osobny stos odwołan do jego
definicji
Niezmiennik: w trakcie analizy, dla kazdego identyfikatora na
szczycie stosu jest odsyłacz do aktualnej definicji (lub stos pusty).
Wejscie do zakresu: przechodzimy liste definicji zwiazanych z
zakresem i wkładamy odsyłacze do nich na odpowiednie stosy.
Wyjscie z zakresu: przechodzimy ponownie liste definicji i
zdejmujemy odsyłacze ze stosów.
W porównaniu z Metoda I nieco wiecej pracy na granicach zakresów,
ale za to szybsze wyszukiwanie.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 25 pazdziernika 2011 34 / 37
Zagadka
class A {
char a;
A() { a = ’A’;}
}
class B {
char a;
B() { a = ’B’; }
class C extends A {
public char c;
C() { c = a; }
}
C C() { return new C(); }
}
...
B b = new B(); B.C c = b.C();
Jaka wartosc ma c.c ?
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 25 pazdziernika 2011 35 / 37
Przykład
global:
type int
class Object,A,B
Object
A:
int a
B:
int a
con B()
class C
C C()
C:
int c
con C()
Java odwiedza najpierw czerwona krawedz.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 25 pazdziernika 2011 37 / 37
Systemy typów
System typów — zbiór typów i reguł wnioskowania o poprawnosci
typowej konstrukcji jezyka (głównie wyrazen)
Reguły sa zwykle wyrazane w postaci
A1 . . . An
B
oznaczajacej “jesli A1 i . . . i An to mozemy wnioskowac B”.
Uzywamy tez notacji
Γ ⊢ e : τ
znaczacej “w srodowisku Γ, wyrazenie e ma typ τ ”.
Srodowisko przypisuje zmiennym typy, tzn. jest zbiorem par (x : τ),gdzie x jest zmienna zas τ typem.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 pazdziernika 2012 2 / 38
Prosty system typów
Typy:
τ ::= int | bool
Wyrazenia:
e ::= n | b | e1 + e2 | e1 = e2 | if e0 then e1 else e2
Reguły:
n : int b : bool
e1 : int e2 : int
e1 + e2 : int
e1 : int e2 : int
e1 = e2 : bool
e0 : bool e1 : τ e2 : τ
if e0 then e1 else e2 : τ
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 pazdziernika 2012 3 / 38
Zmienne
Rozszerzmy nasz jezyk o zmienne:
e ::= x | n | b | e1 + e2 | e1 = e2 | if e0 then e1 else e2
Typ zmiennej zalezy od kontekstu, rozszerzymy zatem nasze reguły
typowania o informacje o kontekscie (srodowisko).
Bedziemy uzywac notacji
Γ ⊢ e : τ
znaczacej “w srodowisku Γ, wyrazenie e ma typ τ ”.
Srodowisko przypisuje zmiennym typy, tzn. jest zbiorem par (x : τ),gdzie x jest zmienna zas τ typem.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 pazdziernika 2012 5 / 38
Reguły typowania w kontekscie
Stałe maja z góry ustalone typy:
Γ ⊢ n : int Γ ⊢ b : bool
Typy zmiennych odczytujemy ze srodowiska:
Γ(x : τ) ⊢ x : τ
Γ ⊢ e1 : int Γ ⊢ e2 : int
Γ ⊢ e1 + e2 : int
Γ ⊢ e1 : int Γ ⊢ e2 : int
Γ ⊢ e1 = e2 : bool
Γ ⊢ e0 : bool Γ ⊢ e1 : τ Γ ⊢ e2 : τ
Γ ⊢ if e0 then e1 else e2 : τ
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 pazdziernika 2012 6 / 38
Kontrola typów w jezykach imperatywnychRozwazmy mały jezyk imperatywny:
e ::= x | n | b | e1 + e2 | e1 = e2
s ::= x := e | while e do s | s; s
Wprowadzimy nowy osad dla programów
Γ ⊢P s
o znaczeniu “w srodowisku Γ, program s jest poprawny.
Niektóre reguły beda uzywac zarówno ⊢ jak ⊢P , np.
Γ ⊢ x : τ Γ ⊢ e : τΓ ⊢P x := e
czyΓ ⊢ e : bool Γ ⊢P p
Γ ⊢P while e do p
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 pazdziernika 2012 7 / 38
DeklaracjeMozemy uznac deklaracje jako rodzaj instrukcji oraz regułe
Γ(x : τ) ⊢P p
Γ ⊢P var x : τ ; p
inna mozliwoscia jest wprowadzenie nowego typu osadu, ⊢D:
Γ ⊢D (var x : τ) : Γ(x : τ)
Γ ⊢D ds : Γ′ Γ′ ⊢P p
Γ ⊢P ds; p
Mozna tez pozwolic instrukcjom na modyfikacje srodowiska.
Deklaracje i instrukcje moga byc wtedy swobodnie przeplatane:
Γ ⊢P s : Γ′ Γ′ ⊢P p : Γ′′
Γ ⊢P s; p : Γ′′
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 pazdziernika 2012 8 / 38
Kontrola typów w jezykach funkcyjnych
Typy:
τ ::= int | bool | τ1 → τ2
Wyrazenia:
E ::=x | n | b | e1e2 | λ(x :τ).e | e1 + e2 | e1 = e2 |
if e0 then e1 else e2
Reguły typowania
Γ(x : τ) ⊢ e : ρ
Γ ⊢ λ(x : τ).e : τ → ρ
Γ ⊢ e1 : τa → τr Γ ⊢ e2 : τ2 τa = τ2
Γ ⊢ e1e2 : τr
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 pazdziernika 2012 9 / 38
Rekonstrukcja typów
Jesli typy nie sa znane, trzeba zrekonstruowac pasujace typy.
Reguły typowania pozostaja te same; reguła dla funkcji
odpowiada zmienionej składni:
Γ(x : τ) ⊢ e : ρ
Γ ⊢ λx .e : τ → ρ
co prowadzi do problemu: skad wziac dobre τ?
Mozemy uczynic τ niewiadoma.
Proces typowania da nam typ wraz z układem równan
Przy kazdym uzyciu reguły aplikacji
Γ ⊢ e1 : τ1 → τ Γ ⊢ e2 : τ2 τ1 = τ2
Γ ⊢ e1e2 : τ
dodajemy do układu równanie τ1 = τ2.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 pazdziernika 2012 11 / 38
Przykłady rekonstrukcji typów
Mozemy podobnie jak w Haskellu traktowac a + b jako aplikacje
(+) a b.
x : τx ⊢ x : τx x : τx ⊢ 1 : int
x : τx ⊢ x + 1 : int{τx = int}
⊢ λx .x + 1 : τx → int ⊢ 7 : int
⊢ (λx .x + 1) 7 : int
z niemal trywialnym układem równan {τx = int}.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 pazdziernika 2012 12 / 38
Przykłady rekonstrukcji typów
Podobnie mozemy uzyskac
⊢ (λf .λx .f (fx))(λy .y) 7 : τ ′f
Z równaniami:
τf = τx → τ ′f (1)
τf = τ ′f → τ ′f (2)
τf → (τx → τ ′f ) = (τy → τy ) → (τx → τ ′f ) (3)
τx → τ ′f = int → τ ′f (4)
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 pazdziernika 2012 13 / 38
Rozwiazywanie równan: unifikacja
Otrzymane układy mozemy rozwiazywac niemal tak samo jak kazde
inne: przez upraszczanie.
W naszym przykładzie mozemy uproscic równanie (4)
τx → τ ′f = int → τ ′f
do
τx = int
i podstawic int za x w pozostałych, otrzymujac
τf = int → τ ′fτf = τ ′f → τ ′f
τf → (int → τ ′f ) = (τy → τy ) → (int → τ ′f )
τx = int
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 pazdziernika 2012 14 / 38
τf = int → τ ′f (5)
τf = τ ′f → τ ′f (6)
τf → (int → τ ′f ) = (τy → τy ) → (int → τ ′f ) (7)
τx = int (8)
Dalej mozemy połaczyc (5) z (6) otrzymujac
int → τ ′f = τ ′f → τ ′f
co moze byc uproszczone do
τ ′f = int.
Po podstawieniu int za τ ′f , mamy
τf = int → int (9)
τ ′f = int (10)
τf → (int → int) = (τy → τy ) → (int → int) (11)
τx = int (12)
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 pazdziernika 2012 15 / 38
Upraszczajac (11) i podstawiajac τf mamy
int → int = τy → τy
skad ostatecznie
τf = int → int (13)
τ ′f = int (14)
τy = int (15)
τx = int (16)
Opisany proces rozwiazywania równan nazywamy unifikacja. W
przypadku sukcesu wynikiem jest podstawienie.
Fakt: unifikacja moze byc zastosowana do rozwiazywania równan na
termach nad dowolna sygnatura. Rozstrzygalna w czasie liniowym.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 pazdziernika 2012 16 / 38
Kiedy unifikacja zawodzi
Unifikacja zawodzi, gdy napotka jedno z ponizszych:
Równanie postaci (k1 i k2 sa róznymi stałymi)
k1 = k2
Równanie postaci (k — stała):
k = t → t ′
Równanie postaci
x = t
gdzie x — zmienna a t zawiera x ale rózny od x .
Na przykład, próba wyprowadzenia typu dla λx .xx prowadzi do
τx = τx → ρ.
Ten term nie jest typowalny (w tym systemie).
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 pazdziernika 2012 17 / 38
Polimorfizm
Z drugiej strony, układ równan moze miec wiecej niz jedno
rozwiazanie. W efekcie mozemy wyprowadzic wiecej niz jeden typ dla
danego wyrazenia. Na przykład, mamy
⊢ λx .x : τ → τ
dla kazdego typu τ !
Dla opisu tego zjawiska mozemy wprowadzic nowa postac typu: ∀α.τ ,
gdzie α jest zmienna typowa, oraz dwie nowe reguły:
Γ ⊢ e : τΓ ⊢ e : ∀α.τ
α 6∈ FV (Γ)Γ ⊢ e : ∀α.τΓ ⊢ e : τ [ρ/α]
(τ [ρ/α] oznacza typ τ z ρ podstawionym za α).
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 pazdziernika 2012 18 / 38
Polimorfizm — przykłady i smutna konstatacja
Mozemy wyprowadzic
⊢ λx .x : ∀α.α → α
Takze λx .xx staje sie typowalne:
⊢ λx .xx : ∀β(∀α.α → α) → β → β
Niestety nowy system nie jest juz sterowany składnia: nowe reguły nie
odpowiadaja zadnym konstrukcjom składniowym i nie wiemy kiedy je
stosowac. Okazuje sie, ze rekonstrukcja typów w tym systemie jest
nierozstrzygalna.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 pazdziernika 2012 19 / 38
Płytki polimorfizm
Rekonstrukcja typów jest rozstrzygalna jesli wprowadzimy pewne
ograniczenie: kwantyfikatory sa dopuszczalne tylko na najwyzszym
poziomie oraz mamy specjalna składnie dla wiazan polimorficznych:
Γ ⊢ e1 : τ1 Γ(x : ∀~α.τ1) ⊢ e : τ
Γ ⊢ let x = e1 in e : τ
Taki system jest czesto wystarczajacy w praktyce. Na przykład
mozemy zastapic konstrukcje if funkcja
if_then_else_ : ∀α.bool → α → α → α
Jest on równiez podstawa systemów dla ML i Haskella (choc ten
ostatni jest znacznie bardziej skomplikowany).
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 pazdziernika 2012 20 / 38
Przeciazanie
Funkcje polimorficzne działaja niezaleznie od typu argumentów.
Przeciazanie oznacza, ze jeden symbol funkcyjny (operator)
oznacza rózne funkcje dla róznych typów argumentów.
Podczas kontroli typów przeciazone symbole sa zastepowane
przez ich warianty odpowiednie dla typów argumentów.
W systemie typów mozemy to wyrazic nastepujaco:
Γ ⊢ e e′ : τ
co oznacza “w srodowisku Γ, wyrazenie e ma typ τ i jest
przekształcane do e′”.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 pazdziernika 2012 21 / 38
Maszyna wirtualna Javy
Typy danych
Typy bazowe: całkowite (int, etc.), zmiennoprzecinkowe (float,
double)
Referencje do obiektów
Obszary danych
Zmienne lokalne i parametry sa przechowywane na stosie.
Stos słuzy tez do obliczen.
Obiekty (w tym tablice) przechowywane na stercie.
Stałe zmiennoprzecinkowe i napisowe przechowywane w
obszarze stałych — nie musimy sie tym przejmowac jesli
uzywamy Jasmina.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 pazdziernika 2012 22 / 38
Stos JVM
Stos jest ciagiem ramek. Kazda instancja metody ma swoja
ramke.
Rózne postaci wywołania:◮ invokestatic dla metod statycznych (np. dla funkcji Latte)◮ invokevirtual dla metod obiektowych◮ invokespecial np. dla konstruktorów (dawniej
invokenonvirtual)
JVM zajmuje sie kwestiami porzadkowymi, jak:◮ alokacja i zwalnianie ramek◮ przekazywanie parametrów◮ dostarczanie wyników
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 pazdziernika 2012 23 / 38
Struktura ramki stosuRamka zawiera zmienne lokalne (w tym parametry) i stos operandów
(dla obliczen). Rozmiary tych obszarów musza byc znane podczas
kompilacji.
Obszar zmiennych lokalnych
Tablica słów przechowujaca argumenty i zmienne lokalne
double zajmuja po dwa słowa, int, referencje — jedno.
Dla metod instancyjnych pod indeksem 0 jest this, dla
statycznych — pierwszy argument.
Stos operandów
Element miesci wartosc dowolnego typu.a
Przed wywołaniem kładziemy argumenty na stosie, po powrocie
wynik tamze.
adouble zajmuje dwie komórki, ale nie trzeba sie tym przejmowac
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 pazdziernika 2012 24 / 38
Instrukcje JVM
Maszyna stosowa
load n załaduj n-ta zmienna lokalna (takze
parametry)
store n zapisz wartosc ze stosu do zmiennej
lokalnej
push val wstaw stała na stos
add, sub, mul,. . . operacje arytmetyczne
ldc stała załaduj stała z tablicy stałych
getfield vname cname pobierz pole z obiektu
getstatic vname cname pobierz pole z klasy
putfield vname cname ustaw pole obiektu
Instrukcje takie jak load, store,add sa prefiksowane typami,
zatem np. aload, istore, fadd,. . .
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 pazdziernika 2012 28 / 38
JVM — tablice
Instrukcje
newarray typ — utwórz tablice (rozmiar na stosie)
iaload załaduj element tablicy int (tablica i indeks na stosie)
aastore zapisz do tablicy referencji (tablica, indeks, wartosc na
stosie)
Przykład
public class Arr {
public static void main(String argv[]){
int[] a = new int[3];
a[2] = 42;
System.out.println(argv[1]);
}
}
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 pazdziernika 2012 34 / 38
JVM — tabliceKod JVM (interesujacy fragment)
; int[] a = new int[3];
iconst_3
newarray int
astore_1
; a[2] = 42;
aload_1
iconst_2
bipush 42
iastore
; System.out.println(argv[1]);
getstatic java/lang/System/out Ljava/io/PrintStream;
aload_0
iconst_1
aaload
invokevirtual
java/io/PrintStream/println(Ljava/lang/String;)V
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 pazdziernika 2012 35 / 38
JVM — obiekty/atrybutyDyrektywa .field typ
Instrukcje
new typ
getfield klasa/pole typ
putfield klasa/pole typ
public class Lista {
int car;
Lista cdr;
static int cadr(Lista a) {
return a.cdr.car;
}
public static void main(String args[]) {
Lista l = new Lista();
l.car = 42;
l.cdr = new Lista();
System.out.println(cadr(l));
}}Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 pazdziernika 2012 36 / 38
JVM — obiekty/atrybuty
Kod JVM (interesujacy fragment)
.field car I
.field cdr LLista;
.method static cadr(LLista;)I
aload_0
getfield Lista/cdr LLista;
getfield Lista/car I
ireturn
.end method
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 pazdziernika 2012 37 / 38
JVM — obiekty/atrybuty
.method public static main([Ljava/lang/String;)V
new Lista
dup
invokespecial Lista/<init>()V
astore_1
aload_1
bipush 42
putfield Lista/car I
aload_1
new Lista
dup
invokespecial Lista/<init>()V
putfield Lista/cdr LLista;
invokestatic Lista/cadr(LLista;)I
invokevirtual java/io/PrintStream/println(I)V
return
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 pazdziernika 2012 38 / 38
LLVM
Low Level Virtual Machine, http://llvm.org/
maszyna rejestrowa, nieograniczona ilosc rejestrów
generacja kodu na rzeczywisty procesor przez alokacje rejestrów
(kolejny wykład)
biblioteka C++, ale takze format tekstowy
kod trójadresowy (dwa zródła, jeden wynik):
%t2 = add i32 %t0, %t1
jeden z argumentów moze byc stała:
%t2 = add i32 %t0, 2
instrukcje sa silnie typowane:
%t5 = add double %t4, %t3
store i32 %t2, i32* %loc_r
nowy rejestr dla kazdego wyniku (Static Single Assignment)
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 15 listopada 2012 2 / 34
LLVM — przykład
declare void @printInt(i32) ; w innym module
define i32 @main() {
%i1 = add i32 2, 2
call void @printInt(i32 %i1)
ret i32 0
}
$ llvm-as t2.ll
$ llvm-ld t2.bc runtime.bc
$ lli a.out.bc
4
Uwaga:
nazwy globalne zaczynaj sie od @, lokalne od %
nazwy zewnetrzne sa deklarowane (@printInt)
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 15 listopada 2012 3 / 34
LLVM — silnia, rekurencyjnie
define i32 @fact(i32 %n) {
%c0 = icmp eq i32 %n, 0
br i1 %c0, label %L0, label %L1
L0:
ret i32 1
L1:
%i1 = sub i32 %n, 1
%i2 = call i32 @fact(i32 %i1)
%i3 = mul i32 %n, %i2
ret i32 %i3
}
Uwaga:
argumenty funkcji sa deklarowane
wszystko jest typowane, nawet warunki skoków
skoki warunkowe tylko z “else”
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 15 listopada 2012 4 / 34
LLVM — typy (nie wszystkie)
n-bitowe liczby całkowite: in, np.:◮ i32 dla int◮ i1 dla bool◮ i8 dla char
nie ma podziału na liczby ze znakiem i bez; sa operacje zeznakiem, np
◮ sle — signed less or equal◮ udiv — unsigned div
float oraz double
label
void
wskazniki: t∗ (np i8* oznacza char*)
tablice: [n ∗ t] (uwaga: inny typ niz wskazniki), np.
@hw = constant [13 x i8] c"hello world\0A\00"
struktury {t1, . . . , tn}
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 15 listopada 2012 5 / 34
LLVM — napisy
Typ char* z C to i8*
char* concat(char* s1,char* s2) {
char* t = malloc(strlen(s1)+strlen(s2)+1);
return strcat(strcpy(t,s1),s2); }
define i8* @concat(i8* %s1, i8* %s2) {
%1 = call i32 @strlen(i8* %s1)
%2 = call i32 @strlen(i8* %s2)
%3 = add i32 %1, 1
%4 = add i32 %3, %2
%5 = call i8* @malloc(i32 %4)
%6 = call i8* @strcpy(i8* %5, i8* %s1)
%7 = call i8* @strcat(i8* %6, i8* %s2)
ret i8* %7 }
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 15 listopada 2012 6 / 34
LLVM — hello
Literały napisowe sa tablicami [n x i8]
Trzeba je rzutowac do i8* np. przez bitcast
@hellostr = internal constant [6 x i8] c"Hello\00"
declare i32 @puts(i8*)
define i32 @main() {
entry:
%t0 = bitcast [6 x i8]* @hellostr to i8*%_ = call i32 @puts(i8* %t0)
ret i32 0
}
Zmienne/stałe globalne reprezentuja wskazniki do swojej zawartosci,
dlatego uzycie @hellostr ma typ [n x i8]*
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 15 listopada 2012 7 / 34
LLVM — tablice
void print7(char a[]) {
puts(&a[6]);
}
int main(){ print7("Hello world!\n");}
define void @print7(i8* %a) {
%x = getelementptr i8* %a, i32 6
%r = call i32 @puts(i8* %x)
ret void
}
define i32 @main() { // char[14] * @str
%x = getelementptr [14 x i8]* @str, i32 0, i32 0
call void @print7(i8* %x)
ret i32 0
}
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 15 listopada 2012 9 / 34
LLVM — tablice i struktury
struct list {
char hdr[16];
int car;
struct list* cdr;
};
int foo(struct list a[]) {
return a[7].car;
}
%struct.list = type { [16 x i8], i32, %struct.list* }
define i32 @foo(%struct.list* %a) {
%1 = getelementptr %struct.list* %a, i32 7, i32 1
%2 = load i32* %1
ret i32 %2
}
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 15 listopada 2012 10 / 34
LLVM — bloki proste
Definicja
Blok prosty jest sekwencja kolejnych instrukcji, do której sterowanie
wchodzi wyłacznie na poczatku i z którego wychodzi wyłacznie na
koncu, bez mozliwosci zatrzymania ani rozgałezienia wewnatrz.
Kod LLVM:
etykietowane bloki proste
kazdy blok konczy sie skokiem (ret lub br)
nie ma automatycznego przejscia od ostatniej instrukcji bloku do
pierwszej kolejnego (kolejnosc bloków moze byc swobodnie
zmieniana)
skoki warunkowe maja dwa cele:
br i1 %c0, label %L0, label %L1
blok wejscia do funkcji jest specjalny: nie mozna do niego skoczyc
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 15 listopada 2012 11 / 34
Rejestry8 32-bitowych rejestrów:
EAX,EDX, EBX,ECX, ESI, EDI, ESP (wskaznik stosu), EBP (wskaznik
ramki)
Flagi
Rejestr EFLAGS składa sie z pól bitowych zwanych flagami,
ustawianych przez niektóre instrukcje i uzywanych głównie przy
skokach warunkowych
ZF — zero
SF — znak (sign)
CF — przeniesienie (carry)
OF — nadmiar/niedomiar (overflow)
Do flag wrócimy przy omówieniu testów i skoków warunkowych.
Architektura x86_64
16 64-bitowych rejestrow: RAX,. . . ,RBP,R8,. . . ,R15.
Nadal mozna uzywac rejestrów 32-bitowych np. EAX, R8D oznaczaja
połówki odpowiednio RAX, R8.Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 15 listopada 2012 17 / 34
Operandy (czyli argumenty instrukcji)
Instrukcja składa sie z tzw. mnemonika (kodu operacji) oraz 0–2
operandów (argumentów), którymi moga byc:
rejestr (r32/r64)
stała (immediate operand, i8/i16/i32/i64),
pamiec (m8/m16/m32/m64)
Najwyzej jeden z operandów moze odwoływac sie do pamieci
AT&T: rejestry prefiksowane %, stałe prefiksowane znakiem $
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 15 listopada 2012 18 / 34
Rozmiary operandów
x86 moze operowac na wartosciach 8, 16, 32 lub 64-bitowych.
Przewaznie z kontekstu wynika jaki rozmiar mamy na mysli, czasem
jednak trzeba to explicite wskazac.
W składni Intela wskazujemy to poprzedzajac operand prefiksem
byte, word, dword lub qword, np (NASM)
MOV [ESP], DWORD hello
W składni AT&T przez sufiks b (8), w (16), l (32), lub q (64) instrukcji,
np.
movl $C0, (%esp)
NB kod generowany przez gcc zawsze dodaje takie sufiksy.
Tutaj pomijamy te sufiksy tam, gdzie nie sa niezbedne.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 15 listopada 2012 19 / 34
Tryby adresowania pamieciW ogólnosci adres moze byc postaci
baza + mnoznik ∗ indeks + przesuniecie
gdzie baza i indeks sa rejestrami, na przykład
EAX+4*EDI+7
Dodatkowe ograniczenia:
ESP nie moze byc indeksem (pozostałe 7 rejestrów moze)
dopuszczalne mnozniki: 1,2,4,8
Składnia adresów
Intel: [baza+mnoznik*indeks+przesuniecie]
AT&T: przesuniecie(baza,indeks,mnoznik)
Najczesciej uzywamy trybu baza + przesuniecie, np.
mov 8(%ebp), %eax
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 15 listopada 2012 20 / 34
Instrukcje przesyłania
Przypisanie
Intel: MOV dest, src
na przykład:
MOV EAX, [EBP-20h]
AT&T: mov src, dest
na przykład
mov -0x20(%ebp), %eax
Instrukcja MOV nie moze przesłac miedzy dwoma komórkami pamieci.
Zamiana
XCHG x, y zamienia zawartosc swoich argumentów
Instrukcje przesyłania nie zmieniaja flag.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 15 listopada 2012 21 / 34
Operacje na stosiePUSH src np.
PUSH [EBP+4]
PUSH DWORD 0
push %ebp
pushl 0
POP dest np.
pop 4(%ebp)
POP [EBP+4]
PUSHA/POPA — połóz/odtwórz wszystkie 8 rejestrów.
Uwaga:
operacje na stosie uzywaja i automatycznie modyfikuja ESP,
stos rosnie w dół — PUSH zmniejsza ESP,
ESP wskazuje na ostatni zajety element stosu.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 15 listopada 2012 22 / 34
Operacje arytmetyczne
ADD x, y
SUB x, y
INC x
DEC x
NEG x
Podobnie jak dla MOV, w składni Intela wynik w pierwszym
argumencie, w AT&T — w drugim
Flagi ustawiane w zaleznosci od wyniku. W przypadku przepełnienia
ustawiana jest flaga OF
Przykład
dodaj zawartosc rejestru ESI do komórki pod adresem EBP+6:
Intel: ADD [EBP+6], ESI
AT&T: add %esi, 6(%ebp)
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 15 listopada 2012 23 / 34
Mnozenie
mnozenie przez 2n mozna wykonac przy pomocy przesuniecia o n
bitów w lewo (instrukcja SAL), np mnozenie przez 16
Intel: SAL EAX, 4
AT&T: sal $4, %eax
mnozenie ze znakiem: IMUL;mnozna (i iloczyn) musi byc w rejestrze,
mnoznik w rejestrze lub pamieci
Przykład
pomnóz ECX przez zawartosc komórki pod adresem ESP
Intel: IMUL ECX, [ESP]
AT&T: imul (%esp), %ecx
Specjalna forma z jednym argumentem (mnoznikiem): IMUL r/m32
— mnozna w EAX, wynik w EDX:EAX
SAL ustawia flagi, IMUL — tylko OF, CF.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 15 listopada 2012 24 / 34
Dzielenie
dzielenie przez 2n mozna wykonac przy pomocy przesuniecia o n
bitów w prawo z zachowaniem znaku (instrukcja SAR), np dzielenie
przez 256
Intel: SAR EAX, 8
AT&T: sar $8, %eax
IDIV y: dzielna w EDX:EAX, dzielnik w rejestrze lub pamieci, iloraz w
EAX, reszta w EDX
NB: przy dzieleniu dwóch liczb 32-bitowych przed IDIV nalezy dzielna
załadowac do EAX, a jej znak do EDX, czyli jesli dzielna dodatnia to
EDX ma zawierac 0, jesli ujemna to -1. Mozna ten efekt uzyskac przez
przesuniecie o 31 bitów w prawo (albo uzywajac instrukcji CDQ).
SAR ustawia flagi, IDIV — nie.
IDIV zajmuje 43 cykle procesora [ADD r32, r32 — 2 cykle; IMUL 9–38,
dokładniej max(⌈log m⌉, 3) + 6 dla mnoznika m].
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 15 listopada 2012 25 / 34
DzieleniePrzykład (AT&T):
mov 28(%esp), %eax
mov %eax, %edx
sar $31, %edx
idivl 24(%esp)
Przykład: (Intel)
MOV EAX, [ESP+28]
MOV EDX, EAX
SAR EDX, 31
IDIV DWORD [ESP+24]
Z uzyciem CDQ:
movl 28(%esp), %eax
cdq
idivl 24(%esp)
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 15 listopada 2012 26 / 34
Instrukcje porównania
CMP x, y — ustawia flagi w zaleznosci od róznicy argumentów
ZF jesli róznica jest 0
SF jesli róznica jest ujemna
OF jesli róznica przekracza zakres
CF jesli odejmowanie wymagało pozyczki
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 15 listopada 2012 27 / 34
SkokiSkok bezwarunkowy: JMP etykieta
Skoki warunkowe w zaleznosci od stanu flag; kody jak wynik CMP
Porównania liczb bez znaku:
Mnemoniki CMP skok gdy. . .
JE/JZ = ZF = 1
JNE/JNZ 6= ZF = 0
JAE/JNB ≥ CF = 0
JB/JNAE < CF = 1
JA/JNBE > (CF or ZF) = 0
JBE/JNA ≤ (CF or ZF) = 1
Porównania liczb ze znakiem:
Mnemoniki CMP skok gdy. . .
JG/JNLE > ((SF xor OF) or ZF) = 0
JGE/JNL ≥ (SF xor OF) = 0
JL/JNGE < (SF xor OF) = 1
JLE/JNG ≤ ((SF xor OF) or ZF) = 1
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 15 listopada 2012 28 / 34
Porównania — przykład
int cmp(int a, int b) {
if(a>b) return 7;
}
moze zostac skompilowane do
cmp:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
cmpl 12(%ebp), %eax # cmp a, b
jng L4 # skocz jesli warunek NIE zachodzi
movl $7, %eax
movl %eax, %edx
movl %edx, %eax
L4:
popl %ebp
ret
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 15 listopada 2012 29 / 34
Protokół wywołania funkcjiCALL adres skok, sladem powrotu na stosie
RET — skok pod adres na szczycie stosu (zdejmuje go)
Protokół uzywany przez gcc oraz libc
przy wywołaniu na stosie argumenty od konca, slad powrotu
przy powrocie wynik typu int lub wskaznik w EAX
Standardowy prolog:
pushl %ebp
movl %esp, %ebp
subl %esp, $x # zmienne lokalne
Standardowy epilog:
movl %ebp, %esp ; pomijane jesli nic nie zmienia
popl %ebp
ret
Wiecej o protokołach wywołania funkcji — na kolejnych wykładach.Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 15 listopada 2012 30 / 34
Przykład — stałe napisowe
.LC0:
.string "Hello\n"
.globl main
main:
pushl %ebp
movl %esp, %ebp
pushl $.LC0
call puts
movl $0, %eax
popl %ebp
ret
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 15 listopada 2012 31 / 34
Protokół wywołania funkcji x86_64
Liczby całkowite przekazywane w EDI,ESI,EDX,ECX,R8D,. . . ,R15D
wskazniki przekazywane w RDI,RSI,RDX,RCX,R8,. . . ,R15
C0:
.string "Hello\n"
.globl main
main:
pushq %rbp
mov %rsp, %rbp
movq $C0, %rdi
call puts
mov $0, %eax
popq %rbp
ret
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 15 listopada 2012 32 / 34
Sztuczki
Alternatywny epilog:
leave
ret
instrukcja LEAVE przywraca ESP i EBP (istnieje tez ENTER, ale jest
za wolna)
TEST x, y - wykonuje bitowy AND argumentów, ustawia SF i ZF
zaleznie od wyniku, zeruje OF i CF
Najczestsze uzycie: ustalenie czy zawartosc rejestru EAX jest
dodatnie/ujemne/zero
Intel: TEST EAX,EAX
AT&T: test %eax, %eax
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 15 listopada 2012 33 / 34
Sztuczki
LEA — ładuje do rejestru wyliczony adres (który moze byc postaci
moze byc postaci baza+mnoznik*indeks+przesuniecie). Moze byc
wykorzystane do wykonania jedna instrukcja ciekawych operacji
arytmetycznych
Przykład
EAX := EBX+2*ECX+1
Intel: LEA EAX, [EBX+2*ECX+1]
AT&T: lea 1(%ebx,%ecx,2), %eax
Skoki posrednie
CALL r/m32 — wywołanie funkcji o adresie zawartym w
rejestrze/komórce pamieci — moze byc uzyte do realizacji metod
wirtualnych
JMP r/m32 — skok jak wyzej, moze byc uzyty do implementacji
instrukcji switch.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 15 listopada 2012 34 / 34
Kod posredni?
Nie jest niezbedny — zwłaszcza przy generacji kodu na maszyne
stosowa.
Przy generacji kodu na rózne architektury wspólne transformacje
niezalezne od architektury.
Ułatwia niektóre optymalizacje.
Rózne postaci — tu zajmiemy sie najbardziej popularna: kodem
czwórkowym.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 19 listopada 2011 2 / 38
Kod czwórkowy
Czwórka:
w := a1 ⊕ a2
argumenty a1, a2
operacja ⊕
lokalizacja wyniku w
Zwany równiez kodem trójadresowym, wiekszosc instrukcji zawiera
bowiem trzy adresy: wyniku i dwu argumentów.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 19 listopada 2011 3 / 38
Instrukcje
Przypisanie postaci x := y op z, gdzie op to jeden z
operatorów +,-,*,/,and,or,xor.
Przypisanie jednoargumentowe postaci x := op y, gdzie op to
jeden z -, not.
Kopiowanie postaci x := y.
Skoki bezwarunkowe postaci goto L, gdzie L to adres w kodzie
zazwyczaj reprezentowany przez etykiete;
przed kazda instrukcja moze wystapic etykieta odnoszaca sie do
pierwszego adresu wystepujacego po niej.
Skoki warunkowe postaci if x oprel y goto L, gdzie oprel
to operator relacyjny (<, >, =, <=, >=, !=).
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 19 listopada 2011 4 / 38
Instrukcje c.d.
Wywoływanie funkcji, obsługiwane przez dwa rozkazy: param x i
t := call L, n, słuzace odpowiednio do przekazania x jako
kolejnego parametru funkcji oraz wywołania funkcji pod adresem
L z n parametrami.
Powrót z funkcji dokonywany jest przez rozkaz return x, gdzie x
to wartosc zwracana.
Przypisania indeksowane postaci x := y[i] oraz x[i] := y.
Pierwszy z tych rozkazów powoduje umieszczenie pod adresem x
zawartosci pamieci spod adresu y + i, drugi - umieszczenie pod
adresem x + i wartosci spod adresu y.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 19 listopada 2011 5 / 38
Kolejnosc obliczen
Rozwazmy wyrazenie e1 + e2, przy czym obliczenie e1 wymaga k
zmiennych tymczasowych (rejestrów), zas e2 — n zmiennych
(załózmy, ze n > k ).
Jesli mozemy obliczac e1 i e2 w dowolnym porzadku, to którelepiej obliczyc najpierw, aby zuzyc jak najmniej zmiennychtymczasowych?
1 “najpierw łatwiejsze”: k rejestrów dla obliczenia e1, potem 1
przechowujacy jego wartosc plus n dla obliczenia e2
max(k , 1 + n) = 1 + n
2 “najpierw trudniejsze”
max(n, 1 + k) = n
Kolejnosc wyliczenia moze zadecydowac, czy uda nam sie
obliczyc wyrazenie tylko przy uzyciu rejestrów (bez odsyłania
wyników posrednich do pamieci).
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 19 listopada 2011 10 / 38
Petla while
while(warunek) instrukcja
Mozna wygenerowac kod nastepujacy:
L1: kod warunku, wynik w t
if not t goto L2
kod instrukcji
goto L1
L2: ...
Mozna tez troche inaczej:
goto L2
L1: kod instrukcji
L2: kod warunku, wynik w t
if t goto L1
W pierwszym wariancie na n obrotów petli wykonujemy 2n skoków, w
drugim — n + 2.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 19 listopada 2011 20 / 38
Instrukcja warunkowa
if(warunek) instrukcja1 else instrukcja2
Mozna wygenerowac kod nastepujacy:
kod warunku, wynik w t
if not t goto Lfalse
Ltrue: kod instrukcji1
goto Lend
Lfalse: kod instrukcji2
Lend: ...
lub
kod warunku, wynik w t
if t goto Ltrue
Lfalse: kod instrukcji2
goto Lend
Ltrue: kod instrukcji1
Lend: ...
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 19 listopada 2011 21 / 38
Skrócone tłumaczenie wyrazen logicznych
Wyrazenia logiczne mozna tłumaczyc albo tak jak wyrazenia
arytmetyczne, albo przy uzyciu tzw. kodu skaczacego
w1&&w2
if not w1 goto Lfalse
if not w2 goto Lfalse
kod Ltrue lub goto Ltrue
w1||w2
if w1 goto Ltrue
if w2 goto Ltrue
kod Lfalse lub goto Lfalse
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 19 listopada 2011 22 / 38
Przykład — JVM
if((a>0&&b>0)||(a<0&&b<0)) return 9;
else return 1;
Dalej jest przestrzen dla ulepszen, np.
iload_2
ifle L3 ; pierwszy and falszywy - sprawdz drugi
L4: iload_3
ifgt L0 ; caly warunek prawdziwy
L3: iload_2
ifge L1 ; caly warunek falszywy
L5: iload_3
ifge L1 ; caly warunek falszywy
L0: bipush 9
ireturn
L1:
iconst_1
ireturn
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 19 listopada 2011 26 / 38
Postac SSA (Static Single Assignment)
Analizy i przekształcenia kodu sa łatwiejsze jesli kod jest w
szczególnej postaci: kazda zmienna ma tylko jedna definicje.
Taka postac nazywamy postacia SSA: Static Single Assignment —
statycznie na kazda zmienna jest tylko jedno przypisanie (nic nie stoi
natomiast na przeszkodzie by wykonało sie wiele razy, np. w petli)
LLVM wymaga kodu w postaci SSA
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 19 listopada 2011 28 / 38
Bloki proste
Blok prosty jest sekwencja instrukcji, do której sterowanie wchodzi
wyłacznie na poczatku i z którego wychodzi wyłacznie na koncu, bez
mozliwosci zatrzymania ani rozgałezienia wewnatrz.
Przykład (abstrakcyjny kod posredni):
E: i := n
r := 1
goto L1
L1: if i <= 1 goto L3 else L2
L2: i := i+1
r := r*i
goto L1
L3: return r
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 19 listopada 2011 29 / 38
Graf przepływu sterowania (Control Flow Graph)
Wierzchołkami sa bloki proste, krawedziami mozliwe przejscia:
entry:
i := n
r := 1
goto L1
L1:
if i <= 1 goto L3 else L2
L2:
i := i+1
r := r*i
goto L1
L3: return r
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 19 listopada 2011 30 / 38
Przekształcanie do postaci SSA — blok prosty
W obrebie bloku prostego przekształcenie do postaci SSA jest
trywialne: kazda definicje zmiennej zastepujemy przez definicje nowej
zmiennej, np.
i := n
r := 1
r := r * i
i := i - 1
return r
Zastepujemy przez
i1 := n
r1 := 1
r2 := r1 * i1
i2 := i1 - 1
return r2
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 19 listopada 2011 31 / 38
Kod dla LLVMEliminujac przypisania postaci a := b, otrzymamy kod dla LLVM
z poprzedniego wykładu:
define i32 @fact(i32 %n) {
entry: br label %L1
L1:
%i.1 = phi i32 [%n, %entry], [%i.2, %L2]
%r.1 = phi i32 [1, %entry], [%r.2, %L2]
%c0 = icmp sle i32 %i.1, 1
br i1 %c0, label %L3, label %L2
L2:
%r.2 = mul i32 %r.1, %i.1
%i.2 = sub i32 %i.1, 1
br label %L1
L3:
ret i32 %r.1
} Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 19 listopada 2011 35 / 38
Eliminacja funkcji φ
Podejscie klasyczne: eliminacja φ przed alokacja rejestrów
Zaleta tego podejscia jest jego prostota, powszechnie stosowane.
Podejscie alternatywne: eliminacja φ po alokacji rejestrów
Bardziej skomplikowane, ale umozliwia łatwiejsza alokacje rejestrów
dla kodu w postaci SSA. Jak dotad rzadko uzywane.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 19 listopada 2011 36 / 38
Eliminacja funkcji φ — podejscie klasyczne
Eliminacja φ przed alokacja rejestrów
Uzycie φ na poczatku bloku B
x = φ(B1 : a1,B2 : a2)
zastepujemy przez odpowiednie przypisania na koncu
poprzedników B: x = a1 na koncu B1, x = a2 na koncu B2,
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 19 listopada 2011 37 / 38
Eliminacja funkcji φ — podejscie alternatywne
Eliminacja φ po alokacji rejestrów
R1 = φ( . . . , Bi : Ri1, . . .). . .
Rn = φ( . . . , Bi : Rin, . . .)
na koncu bloku Bi wstawiamy kod realizujacy permutacje
(R1, . . . ,Rn) = (Ri1, . . . ,Rin)
Moze sie tu przydac instrukcja XCHG Ri, Rj
Warto generowac kod taki, aby permutacje były jak najmniejsze
(niestety, NP-trudne).
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 19 listopada 2011 38 / 38
Drzewo aktywacji
Wykonanie programu mozna przedstawic w postaci tzw. drzewa
aktywacji, którego wezły reprezentuja wcielenia (wykonania) funkcji.
Korzen tego drzewa to wykonanie programu głównego a wezeł F
ma synów G1 . . .Gn jesli wcielenie funkcji F wywołało G1, pózniej
G2 itd.
Podczas wykonania programu odwiedzamy wezły porzadku
prefiksowym, od lewej do prawej.
Na sciezce od korzenia do aktualnego wezła sa aktywne wcielenia
funkcji, na lewo juz zakonczone a na prawo jeszcze sie nie
rozpoczete.
Jesli istnieje sciezka, na której wystepuje wiele wcielen tej samej
funkcji, mówimy ze funkcja ta jest rekurencyjna.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 listopada–6 grudnia 2012 3 / 45
Rekord aktywacji
Z kazdym wcieleniem funkcji wiazemy pewne informacje. Obszar
pamieci, w którym sa zapisywane, nazywamy rekordem aktywacji
albo ramka (ang. frame).
W wiekszosci jezyków potrzebne sa tylko rekordy dla aktywnych
wcielen na aktualnej sciezce w drzewie aktywacji.
Gdyby nie rekurencja, dla kazdej funkcji moglibysmy z góry
zarezerwowac obszar pamieci na jedna ramke (wczesny Fortran).
W jezykach z rekurencja rekordy aktywacji alokujemy przy
wywołaniu funkcji a zwalniamy, gdy funkcja sie skonczy.
Rekordy aktywacji przechowujemy wiec na stosie.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 listopada–6 grudnia 2012 4 / 45
Zawartosc rekordu aktywacji
Informacje pamietane w rekordzie aktywacji zaleza m. in. od jezyka.
Moga tam byc:
parametry
zmienne lokalne i zmienne tymczasowe
slad powrotu
kopia rejestrów (wszystkich, niektórych lub zadnego)
łacze dynamiczne (DL, ang. dynamic link) – wskaznik na
poprzedni rekord aktywacji; ciag rekordów połaczonych
wskaznikami DL tworzy tzw. łancuch dynamiczny.
łacze statyczne (SL, ang. static link)
miejsce na wynik
Postac rekordu aktywacji nie jest sztywno okreslona — projektuje ja
autor implementacji jezyka.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 listopada–6 grudnia 2012 5 / 45
Adresowanie rekordu aktywacji
Adres ramki jest zwykle przechowywany w wybranym rejestrze
(FP = frame pointer, BP = base pointer).
Pola rekordu aktywacji sa adresowane przez okreslenie ich
przesuniecia wzgledem adresu w FP.
Kazde wcielenie funkcji, niezaleznie od połozenia rekordu
aktywacji, w ten sam sposób moze korzystac z jego zawartosci, a
wiec wszystkie wcielenia maja wspólny kod.
Adresem rekordu aktywacji nie musi byc adres jego poczatku.
Czasem wygodniej przyjac adres jednego z pól w srodku tego
rekordu.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 listopada–6 grudnia 2012 6 / 45
Adresowanie rekordu aktywacji
Jesli w jezyku wystepuja funkcje ze zmienna liczba argumentów
(jak np. w C), lepiej adresowac wzgledem srodka ramki.
Przy adresowaniu wzgledem poczatku, przesuniecia zaleza od
liczby parametrów, a wiec nie sa znane podczas kompilacji.
Rozwiazanie: adresowanie ramki wzgledem miejsca pomiedzy
parametrami a zmiennymi lokalnymi funkcji.
Parametry zapisujemy kolejnosci od ostatniego do pierwszego,
dzieki czemu przesuniecie K-tego parametru nie zalezy od liczby
parametrów, tylko od stałej K.
Wynik funkcji czesto w rejestrach zamiast na stosie.
Dla architektur z duza liczba rejestrów (np x86_64) niektóre
ergumenty tez w rejestrach.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 listopada–6 grudnia 2012 7 / 45
Pomijanie wskaznika ramki
Aktualny rekord aktywacji zawsze znajduje sie na wierzchołku
stosu; mozna do jego adresowania uzyc wskaznika stosu.
Zalety: oszczedzamy jeden rejestr i kilka instrukcji na kazde
wywołanie.
Wady: wierzchołek stosu przesuwa sie podczas obliczen,
powodujac zmiany przesuniec pól rekordu aktywacji; podatne na
błedy w generowaniu kodu.
Wystepuje w GCC z opcja -fomit-frame-pointer (zatem
przewaznie takze z opcja -O)
Wymaga dodatkowych zabiegów przy obsłudze wyjatków (o czym
pózniej).
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 listopada–6 grudnia 2012 8 / 45
Protokół wywołania (i powrotu z) funkcji
Zaprojektujemy protokół wywołania i powrotu z funkcji. Załozymy przy
tym nastepujaca postac rekordu aktywacji:
miejsce na wynik
parametry
slad
DL
zmienne
Bedziemy uzywac abstrakcyjnego procesora, podobnego do x86, ale
w którym wskaznik/int zajmuje jedno słowo, z rejestrami
SP — wskaznik stosu
BP — wskaznik ramki
A,B,C,D,S — rejestry uniwersalne
Skladnia asemblera zblizona do AT&T.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 listopada–6 grudnia 2012 13 / 45
Protokół wywołania (i powrotu z) funkcjiWskaznikiem rekordu aktywacji bedzie BP zawierajacy adres pola DL.
wołajacy
PUSH 0 # miejsce na wynik
<parametry na stos>
CALL adres_wołanego
ADD n, SP # n - łaczny rozmiar parametrów
# wynik zostaje na stosie
wołany
PUSH BP # DL na stos
MOV SP, BP # aktualizacja wskaznika ramki
SUB k, SP # k - łaczny rozmiar zmiennych
... # tłumaczenie tresci funkcji
MOV BP, SP # przywracamy wskaznik stosu
POP BP # przywracamy wskaznik ramki
RET # powrót do wołajacego
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 listopada–6 grudnia 2012 14 / 45
Mechanizm przekazywania parametrów
Przekazywanie parametru przez wartosc:
wołajacy umieszcza w rekordzie aktywacji wartosc argumentu;
wołany moze odczytac otrzymana wartosc, ew. zmieniac ja
traktujac parametr tak samo, jak zmienna lokalna;
ewentualne zmiany nie sa widziane przez wołajacego.
Przekazywanie parametru przez zmienna (referencje)
wołajacy umieszcza w rekordzie aktywacji adres zmiennej;
wołany moze odczytac wartosc argumentu siegajac pod ten adres,
moze tez pod ten adres cos wpisac, zmieniajac tym samym
wartosc zmiennej bedacej argumentem.
Jesli argumenty sa przekazywane w rejestrach, wołany musi zwykle
zapisac je w swojej czesci rekordu aktywacji.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 listopada–6 grudnia 2012 15 / 45
Srodowisko w jezykach ze struktura blokowa
Wiele jezyków programowania (n.p. Pascal) pozwala na
zagniezdzanie funkcji i procedur. Jezyki te nazywamy jezykami ze
struktura blokowa.
Kod funkcji ma dostep nie tylko do jej danych lokalnych, ale takze
do danych funkcji, w której jest zagniezdzona itd. az do poziomu
globalnego.
Działanie funkcji jest okreslone nie tylko przez jej kod oraz
parametry, lecz takze przez srodowisko, w którym ma sie
wykonac.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 listopada–6 grudnia 2012 20 / 45
Wiazanie statyczne i dynamiczne
Postac srodowiska jest w Pascalu wyznaczona statycznie –– z
kodu programu wynika, do której funkcji nalezy rekord aktywacji,
w którym mamy szukac zmiennej nielokalnej.
Mówimy, ze w Pascalu obowiazuje statyczne wiazanie zmiennych.
Istnieja równiez jezyki (n.p. Lisp), w których obowiazuje wiazanie
dynamiczne –– w przypadku odwołania do danej nielokalnej,
szukamy jej w rekordzie aktywacji wołajacego itd. w góre po
łancuchu dynamicznym.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 listopada–6 grudnia 2012 21 / 45
Łacze statyczne
By korzystac z danych nielokalnych, działajaca funkcja musi miec
dostep do swojego srodowiska.
Moglibysmy przekazac jej wszystkie potrzebne dane znajdujace
sie w jej srodowisku jako dodatkowe parametry. Rozwiazanie
takie stosuje sie czesto w jezykach funkcyjnych.
W jezykach imperatywnych najczesciej stosowanym
rozwiazaniem jest powiazanie w liste ciagu ramek, które sa na
sciezce w hierarchii zagniezdzania.
Kazda ramka zawiera łacze statyczne (SL, static link) – wskaznik
do jednego z rekordów aktywacji funkcji otaczajacej dana.
Rekord ten nazywamy poprzednikiem statycznym, a ciag
rekordów połaczonych SL to łancuch statyczny.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 listopada–6 grudnia 2012 22 / 45
Wyliczanie SL
SL musi byc liczony przez wołajacego, bo do jego okreslenia
trzeba znac zarówno funkcje wołana jak i wołajaca.
Srodowisko dla funkcji wołanej zalezy od srodowiska wołajacej –
jesli obie widza zmienna x , jej wartosc ma byc dla nich równa.
Jesli funkcja F znajdujaca sie na poziomie zagniezdzenia Fp
wywołuje G z poziomu zagniezdzenia Gp, w pole SL wpisze adres
rekordu, który odnajdzie przechodzac po własnym łancuchu
statycznym o Fp − Gp + 1 kroków w góre.
SL dla G ma wskazywac na rekord aktywacji funkcji na poziomie
zagniezdzenia Gp − 1, o δ kroków w łancuchu aktywacji od
ramki F :
Gp − 1 = Fp − δ
δ = Fp − Gp + 1
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 listopada–6 grudnia 2012 23 / 45
Wyliczanie SL
Jesli funkcja F znajdujaca sie na poziomie zagniezdzenia Fp
wywołuje G z poziomu zagniezdzenia Gp, w pole SL wpisze adres
rekordu, który odnajdzie przechodzac po własnym łancuchu
statycznym o Fp − Gp + 1 kroków w góre.
Jesli np. G jest funkcja lokalna F (czyli Gp = Fp + 1), funkcja F w
pole SL dla G wpisze adres swojego rekordu aktywacji
(Fp − Gp + 1 = 0)
Jesli F i G sa na tym samym poziomie zagniezdzenia, w polu SL
dla G bedzie to, co w SL dla F (Fp − Gp + 1 = 1)
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 listopada–6 grudnia 2012 24 / 45
Dostep do danych nielokalnych
Dane lokalne funkcji sa w jej rekordzie aktywacji a dane globalne
w ustalonym miejscu w pamieci — mozna do nich siegac za
pomoca adresów bezwzglednych.
Dostep do danych nielokalnych przez SL; W funkcji F o poziomie
Fp siegamy do zmiennej z funkcji G o poziomie Gp przechodzac
Fp − Gp kroków w góre po SL.
Adres zmiennej jest wyznaczony przez poziom zagniezdzenia i
pozycje w rekordzie.
Adresy rekordów z łancucha statycznego mozna tez wpisac do
tablicy (tzw. display). Dzieki temu unikniemy chodzenia po
łancuchu statycznym, ale za to trzeba bedzie stale aktualizowac
tablice.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 listopada–6 grudnia 2012 25 / 45
Funkcje jako argumenty funkcji
W jezykach bez zagniezdzania funkcji, kazda funkcja ma dostep
do zmiennych globalnych oraz własnych zmiennych lokalnych.
W takiej sytuacji wystarczy przekazac adres kodu funkcji.
W jezykach ze struktura blokowa, funkcja moze miec dostep do
danych nielokalnych, który realizowany jest przy pomocy SL.
Jak ustawic SL przy wywołaniu funkcji otrzymanej jako parametr?
Własny SL niekoniecznie jest tu dobrym rozwiazaniem. . .
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 listopada–6 grudnia 2012 31 / 45
Przykład
procedure t(procedure p);
begin
p;
end {t}
function f : int;
var a : int;
procedure x; begin a := 17 end
begin {f}
t(x); f := a
end {f}
W momencie wywołania procedury x prezekazanej jako parametr
t do f, SL dla x musi byc ustawiony na f.
Odpowiedni SL musi byc zatem przekazany razem z adresem
procedury.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 listopada–6 grudnia 2012 32 / 45
Zmienne instancyjne
Kazdy obiekt posiada zmienne zdefiniowane w jego klasie, a
takze zmienne odziedziczone z nadklas.
Reprezentacja obiektów jest analogiczna do rekordów —
w obszarze pamieci zajetym przez obiekt znajduja sie wartosci
jego zmiennych instancyjnych.
Kolejnosc tych zmiennych ma byc zgodna z hierarchia
dziedziczenia — zmienne zdefiniowane w klasie obiektu musza
sie znalezc na koncu, przed nimi sa zmienne z klasy
dziedziczonej itd. w góre hierarchii dziedziczenia.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 listopada–6 grudnia 2012 34 / 45
Zmienne instancyjne
obiekty klasy A beda zawierały jedna zmienna: w
obiekty klasy B trzy zmienne w kolejnosci: w x y
obiekty klasy C cztery zmienne w kolejnosci: w x y z
taka kolejnosc umozliwia metodom danej klasy prawidłowe działanie
zarówno dla obiektów tej klasy, jak i jej podklas.
W obiekcie dziedziczacym zmienna znajduje sie ona w tym samym
miejscu, co w obiektach klasy dziedziczonej.
W naszym przypadku, zarówno w obiektach klasy B jak i C, zmienne
x,y sa odpowiednio na 2 i 3 pozycji.
Metoda writeB wie pod jakim przesunieciem te zmienne sie znajduja
niezaleznie od rzeczywistej klasy obiektu
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 listopada–6 grudnia 2012 36 / 45
Alternatywne rozwiazanie
Mozna dopuscic dowolna kolejnosc fragmentów odpowiadajacych
poszczególnym klasom, np. C B A
Analizator typów przepisze metode B::writeB mniej wiecej tak:
void B::writeB(B *this){
write(this->Aptr->w,this->Bptr->x,this->Bptr->y);
}
Jest to jak widac bardziej złozone i mniej efektywne, jednak daje
wieksza elastycznosc: teraz fragmenty A, B, C nie musza nawet byc
obok siebie (ten fakt za chwile nam sie przyda).
Znane musza byc tylko przesuniecia Aptr, Bptr
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 listopada–6 grudnia 2012 38 / 45
Metody wirtualne
Wywołanie metody w jezykach obiektowych rózni sie od wywołania
funkcji/procedury w jezykach proceduralnych dwoma elementami:
metoda otrzymuje jako dodatkowy ukryty parametr obiekt, dla
którego ma sie wykonac.
w niektórych jezykach wystepuje mechanizm metod wirtualnych:
wybór metody zalezy od rzeczywistej (raczej niz deklarowanej)
klasy obiektu i jest dokonywany podczas wykonania programu,
a nie podczas kompilacji.
Reakcja obiektu na komunikat zalezy od jego klasy. Jesli w tej klasie
jest metoda o nazwie takiej, jak nazwa komunikatu, wywołujemy ja,
jesli nie, to szukamy w nadklasie itd.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 listopada–6 grudnia 2012 39 / 45
Tablica metod wirtualnych
Powszechnie stosowanym rozwiazaniem jest wyposazenie
obiektu w tablice metod wirtualnych, zawierajaca adresy kodu
odpowiednich metod.
W jezykach z typami statycznymi, dopuszczalne komunikaty sa
znane podczas kompilacji. Mozna je ponumerowac
i reprezentowac tablice metod wirtualnych za pomoca zwykłej
tablicy V , gdzie V [k ] zawiera adres metody, która nalezy wykonac
w odpowiedzi na komunikat numer k .
Wysłanie komunikatu k wymaga skoku ze sladem pod adres V [k ].
Wszystkie obiekty danej klasy moga miec wspólna tablice metod
wirtualnych. W samym obiekcie umieszczamy jedynie adres tej
tablicy.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 listopada–6 grudnia 2012 40 / 45
Budowa tablic metod wirtualnych
Budowa tablic metod wirtualnych oraz numerowanie komunikatów
odbywa sie podczas kompilacji, na etapie analizy kontekstowej.
Tablice metod wirtualnych dla poszczególnych klas budujemy w
kolejnosci przejscia drzewa dziedziczenia "z góry na dół".
Tablica metod wirtualnych dla podklasy powstaje z tablicy dla
nadklasy przez dodanie adresów metod zdefiniowanych w tej
klasie.
Jesli metoda była juz zdefiniowana "wyzej" w hierarchii, czyli jest
redefiniowana, jej adres wpisujemy na pozycje metody
redefiniowanej.
Jesli metoda pojawia sie na sciezce dziedziczenia pierwszy raz,
jej adres wpisujemy na pierwsze wolne miejsce w tablicy metod
wirtualnych.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 listopada–6 grudnia 2012 41 / 45
Wielodziedziczenie
struct A {int a;}
struct B:A {int b;}
struct C:A {int c;}
struct D:B,C {}
Jak wyglada obiekt klasy D?
B::A::a B::b C::A::a C::c
Obiektu klasy D mozemy bez problemu uzywac jako obiektu klasy B
Konwersja (D∗) 7→ (B∗) jest identycznoscia
Konwersja (D∗) 7→ (C∗) wymaga przesuniecia wskaznika o rozmiar B
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 listopada–6 grudnia 2012 42 / 45
Wirtualne nadklasy
struct A {int a;}
struct B: virtual A {int b;}
struct C: virtual A {int c;}
struct D:B,C {}
Jak wyglada obiekt klasy D?
A::a B::b C::c
Obiektu klasy D mozemy bez problemu uzywac jako obiektu klasy B
Konwersja (D∗) 7→ (B∗) jest identycznoscia
Zadna arytmetyka na wskaznikach nie zapewni (D∗) 7→ (C∗) Trzeba
uzyc mechanizmu analogicznego do metod wirtualnych
Patrz “Alternatywne rozwiazanie” kilka slajdów wczesniej.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 listopada–6 grudnia 2012 43 / 45
Protokół wywołania i386Istnieje wiele wariantów, tu zajmiemy sie protokołem uzywanym przez
GCC+libc (aka “cdecl”).
przy wywołaniu na stosie argumenty od konca, slad powrotu
wołajacy zdejmuje argumenty
przy powrocie wynik typu int/wskaznik w EAX
rejestry EBP,ESI,EDI,EBX musza byc zachowane
Standardowy prolog:
pushl %ebp
movl %esp, %ebp
subl $x, %esp /* zmienne lokalne */
Standardowy epilog:
movl %ebp, %esp /* pomijane jesli nop */
popl %ebp
ret
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 listopada–6 grudnia 2012 44 / 45
Protokół wywołania x86-64
Liczby całkowite przekazywane w EDI,ESI,EDX,ECX,R8D,R9D
wskazniki przekazywane w RDI,RSI,RDX,RCX,R8,R9
jesli wiecej argumentów, lub wieksze niz 128-bitowe, to na stosie
przy powrocie wynik typu int w EAX; wskaznik w RAX
rejestry RBP, RBX i R12 do R15 musza byc zachowane
Standardowy prolog:
pushl %rbp
movl %rsp, %rbp
subl $x, %rsp /* zmienne lokalne */
Standardowy epilog:
movl %rbp, %rsp /* pomijane jesli nop */
popl %rbp
ret
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 29 listopada–6 grudnia 2012 45 / 45
Graf bloków prostych (przepływu sterowania, CFG)
Zródło:i=m-1; j=n; v=a[n];
while (1) {
do i=i+1; while (a[i]<v);
do j=j-1; while (a[j]>v);
if (i >= j) break;
x=a[i]; a[i]=a[j]; a[j]=x;
}
x=a[i]; a[i]=a[n]; a[n]=x;
L1:
i := m-1
j := n
t1 := 4*n
v := a[t1]
L5:
i := i+1
t2 := 4*i
t3 := a[t2]
if t3<v goto L5
L9:
j := j-1
t4 := 4*j
t5 := a[t4]
if t5>v goto L9
L13:
if i>=j goto L23
L14:
t6 := 4*i
x := a[t6]
t7 := 4*i
t8 := 4*j
t9 := a[t8]
a[t7] := t9
t10 := 4*j
a[t10] := x
goto L5
L23:
t11 := 4*i
x := a[t11]
t12 := 4*i
t13 := 4*n
t14 := a[t13]
a[t12] := t14
t15 := 4*n
a[t15] := x
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 13 grudnia 2012 7 / 60
Analiza zywotnosci
Definicja zmiennej — instrukcja nadajaca wartosc tej zmiennej
Uzycie zmiennej — instrukcja odwołujaca sie do wartosci tejzmiennej
Instrukcja x:=y+z definiuje zmienna x, uzywa zmiennych z oraz y
Definicja
Zmienna jest zywa w danym punkcie, jesli jej obecna wartosc moze bycjeszcze uzyta, tzn. istnieje sciezka od tego punktu do uzycia zmiennej, niezawierajaca po drodze definicji tej zmiennej.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 13 grudnia 2012 8 / 60
Docierajace definicje
Definicja
Definicja dociera do danego punktu, jesli zadna sciezka pomiedzy nimi niezawiera innej definicji tej samej zmiennej.
[ 2] j := n
[ 3] t1 := 4*n
[ 4] v := a[t1]
[ 5] i := i+1
[ 6] t2 := 4*i
[ 7] t3 := a[t2]
[ 8] if t3 < v goto (5)
[ 9] j := j-1
[10] t4 := 4*j
[11] t5 := a[t4]
Definicja t1 z (3) dociera do (11); definicja j z (2) nie dociera do (10).
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 13 grudnia 2012 10 / 60
Analiza przepływu
Informacje dotyczace przepływu danych zwykle wylicza sie zapomoca układów równan, które przedstawiaja zaleznosci pomiedzyróznymi punktami programu.Zwykle równanie (dla przepływu “w przód”) ma postac:
out[S] = gen[S] ∪ (in[S] − kill[S])
Informacja dostepna na koncu instrukcji jest suma zbiorów informacji
przez nia generowanych (gen[S])
dostepnych na wejsciu do niej (in[S])
bez informacji, które sa niszczone przez te instrukcje (kill[S ]).
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 13 grudnia 2012 11 / 60
Analiza przepływu
Ogólne równanie przepływu
out[S] = gen[S] ∪ (in[S] − kill[S])
S moze sie odnosic do pojedynczej czwórki lub bloku prostego.
Definicje gen i kill zaleza od analizowanej informacji
Dla zywotnosci informacja płynie “w tył”;obliczamy in na podstawie out:
in[S] = out[S] − kill[S] ∪ use[S]
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 13 grudnia 2012 12 / 60
Globalna analiza przepływu
Analize przepływów miedzy blokami przeprowadzamy na podstawiekrawedzi wchodzacych i wychodzacych z bloku.
Na przykład dla analizy zywotnosci
out[Bi] =⋃
j∈succ(Bi)
in[Bj]
gdzie succ(Bi) oznacza zbiór nastepników bloku Bi
Dla docierajacych definicji
in[Bi] =⋃
j∈pred(Bi)
out[Bj]
gdzie pred(Bi) oznacza zbiór poprzedników bloku Bi
Graf przepływu z cyklami daje rekurencyjny układ równan
Mozemy go rozwiazac, iterujac do osiagniecia punktu stałego.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 13 grudnia 2012 15 / 60
Generacja kodu maszynowego
Stan maszyny: zawartosc zasobów pamieciowych (rejestrów,stosu, pamieci).
Podstawowa technika: symulacja zachowania maszynydocelowej (ciagu stanów).
Korzystamy z wzajemnie powiazanych opisów zasobów (głównierejestrów) oraz opisów zmiennych i wartosci.
Kazda wartosc wyliczana przez program i kazdy zasób saokreslone przez opisy.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 13 grudnia 2012 21 / 60
Opisy
Opis rejestru
Stan: wolny, zablokowany, etc
Co zawiera (byc moze wiele wartosci)
Opis wartosci
typ, rozmiar
Gdzie jest wartosc (byc moze w wielu miejscach)
Aliasy (np zmienna moze byc dostepna zarówno bezposredniojaki poprzez wskaznik)
Opis wartosci jest interesujacy tylko dla zmiennych zywych
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 13 grudnia 2012 22 / 60
Generacja kodu dla bloku prostego
1 Wyznaczamy bloki proste
2 Okreslamy zmienne zywe na koncu bloku
3 Wyznaczamy nastepne uzycie dla kazdego argumentu i wynikuczwórki
4 Generujemy kod dla kolejnych czwórek, w biegu przydzielajac imrejestry, odkładajac zapis do pamieci, o ile sie da
5 Na koncu bloku zapisujemy wszystkie zywe, a nie zapisane dotadwartosci.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 13 grudnia 2012 24 / 60
Przykład symulacji z opisami rejestrów i wartosci
Zakładamy, ze na koncu bloku d jest zywe.Opisy wartosci przechowujemy tylko dla zmiennych zywych wdanym punkcie.
Instrukcje Kod R0 R1 a b d t u v
- - a b d t u vt := a a b - a - -a := b b b - ab := t b a - -t := a − b mov b,R0 a R0,b a
sub a,R0 t b a R0u := a − c mov b,R1 t a R1,b R0
sub c,R1 t u R0 R1v := t + u add R1,R0 v u R1 R0d := v + u add R1,R0 d u R0
mov R0,d d u R0,d
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 13 grudnia 2012 26 / 60
Co zrobic, jesli nie ma wolnego rejestru (spilling)
Algorytm MIN [Belady 1966] (oryginalnie dla zwalniania stronpamieci wirtualnej)
1 Wybieramy rejestr, przechowujacy wartosc, której uzycie lezynajdalej w przyszłosci
2 Odsyłamy do pamieci (spilling)
3 Jesli rejestr przechowywał wiecej niz jedna wartosc, musimyodesłac wszystkie
Wiele róznych mozliwosci, pole do wielorakich optymalizacji iheurystyk.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 13 grudnia 2012 27 / 60
Globalna alokacja rejestrów
1 Wydzielamy pewna pule r rejestrów; w pewnym fragmencieprogramu wybrane wartosci bedziemy przechowywac na stałe wrejestrach.
2 Tworzymy graf kolizji: wierzchołkami sa zmienne, jesli przydefinicja a, zmienna b jest zywa, to dodajemy krawedz (a, b)
3 Kolorujemy graf r kolorami (uwaga: NP-trudne)
4 Alokacja rejestrów jest NP-trudna [Chaitin 1981]. . .
5 . . . ale dla postaci SSA algorytm O(n2) [Hack,Grund,Goos 2006].
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 13 grudnia 2012 28 / 60
Optymalizacja “przez dziurke od klucza” (peep-hole)
Definiujemy zbiór wzorców krótkich sekwencji kodu, które łatwoulepszyc, np. w sekwencji
MOV Ri, a
MOV a, Ri
druga instrukcja jest zbedna.
Przesuwamy sie wzdłuz wygenerowanego kodu małym“okienkiem” (zwykle 2–3 instrukcje), jesli kod w okienku pasujedo któregos z wzorców — ulepszamy.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 13 grudnia 2012 29 / 60
Zwijanie stałych
Jesli na zmienna przypisywana jest stała, mozemy wszystkie uzycia tejzmiennej w zasiegu definicji zastapic wystapieniami tej stałej,ewentualnie obliczajac wyrazenia w czasie kompilacji.Na przykład sekwencje
t1 := 7
t2 := t1 - 1
t3 := t2 * t2
a := b + t3
Mozemy zastapic przez
a := b + 36
NB dla maszyny stosowej mozna to zrobic na etapie peephole.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 13 grudnia 2012 32 / 60
Propagacja kopii
Podobnie jesli wystepuje kopiowanie x = y wszystkie uzycia x doktórych dociera ta definicja mozna zastapic przez y (SSA pomaga)
entry:
i0 := n
goto L1
L1:
i1 := φ(entry:i0,L2:i2)
r1 := φ(entry:1,L2:r2)
if i1 <= 1 goto L3 else L2
L2:
i2 := i1+1
r3 := r2*i
goto L1
L3: return r1
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 13 grudnia 2012 35 / 60
Wspólne podwyrazenia lokalnie
Wyróznione instrukcjemozna usunac,odpowiednio poprawiajacpozostały kod
i := m-1
j := n
t1 := 4*n
v := a[t1]
L5:
i := i+1
t2 := 4*i
t3 := a[t2]
if t3<v goto L5
L9:
j := j-1
t4 := 4*j
t5 := a[t4]
if t5>v goto L9
if i>=j goto L23
t6 := 4*i
x := a[t6]
t7 := 4*i
t8 := 4*j
t9 := a[t8]
a[t7] := t9
t10 := 4*j
a[t10] := x
goto L5
L23:
t11 := 4*i
x := a[t11]
t12 := 4*i
t13 := 4*n
t14 := a[t13]
a[t12] := t14
t15 := 4*n
a[t15] := x
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 13 grudnia 2012 41 / 60
Konkluzja
Zaczynalismy od 30 czwórek, po optymalizacjach 20 i to tanszych.
Po generacji kodu maszynowego mozemy jeszcze wykonacpeephole.
Uzyskujemy mniejszy i szybszy kod.
Cena: wiekszy i dłuzej działajacy kompilator.
Łatwo popełnic trudny do wykrycia bład.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 13 grudnia 2012 53 / 60
Eliminacja martwego kodu
x,t9 sa martwe, mozemyusunac ich przypisania
i := m-1
j := n
t1 := 4*n
v := a[t1]
L5:
i := i+1
t2 := 4*i
t3 := a[t2]
if t3<v goto L5
L9:
j := j-1
t4 := 4*j
t5 := a[t4]
if t5>v goto L9
if i>=j goto L23
L14:
x := t3
t9 := t5
a[t2] := t5
a[t4] := t3
goto L5
L23:
x := t3
t14 := a[t1]
a[t2] := t14
a[t1] := t3
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 13 grudnia 2012 48 / 60
Wysuniecie kodu przed petle
Obliczenia wyrazen, które nie zmieniaja swej wartosci w trakcie petlimozemy wysunac przed petle.
while(i<=n-3) {
s += a[i];
i++;
}
Mozemy zastapic przez
t = n-3;
while(i<=t) {
s += a[i];
i++;
}
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 13 grudnia 2012 49 / 60
Redukcja mocy i zmienne indukcyjne
Redukcja mocy (strength reduction) polega na zamianie drozszejoperacji (np. mnozenie) przez tansza (np. dodawanie).
Jest to mozliwe i pozyteczne w stosunku do tzw. zmiennychindukcyjnych, czyli takich które sa zwiekszane (ew. zmniejszane) ostała (zwykle 1) za kazdym obrotem petli.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 13 grudnia 2012 50 / 60
Redukcja mocy i zmienne indukcyjneNiezmiennik petli:t2 = 4 ∗ it4 = 4 ∗ jDodajemy zieloneinstrukcje i usuwamyczerwone
i := m-1
j := n
t1 := 4*n
v := a[t1]
t2 := 4*i
t4 := 4*n
L5:
i := i+1
t2 := t2+4
t3 := a[t2]
if t3<v goto L5
L9:
j := j-1
t4 := t4-4
t5 := a[t4]
if t5>v goto L9
if t2>=t4 goto L23
L14:
a[t2] := t5
a[t4] := t3
goto L5
L23:
t14 := a[t1]
a[t2] := t14
a[t1] := t3
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 13 grudnia 2012 52 / 60
Wywołania koncowe (tail calls)
int factorial(int n) {
return _factorial(n, 1);
}
int _factorial(int n, int result) {
if (n <= 0)
return result;
else
return _factorial(n - 1, n * result);
}
Jesli ostatnia instrukcja jest wywołanie funkcji, mozemy je zastapicskokiem.Jesli skok jest do tej samej funkcji (nie musi byc!), jest to tzw. rekursjaogonowa.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 13 grudnia 2012 54 / 60
gcc -O1
_factorial:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
movl 8(%ebp), %edx ; edx = n
movl 12(%ebp), %eax ; eax = result
testl %edx, %edx
jle .L2 ; if n <= 0
imull %edx, %eax ; eax = n * result
movl %eax, 4(%esp) ; na stos
leal -1(%edx), %eax ; eax = n-1
movl %eax, (%esp) ; na stos
call _factorial
.L2:
leave ; przywroc wskaznik ramki
retMarcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 13 grudnia 2012 55 / 60
gcc -O1 -foptimize-sibling-calls
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %edx
movl 12(%ebp), %eax
testl %edx, %edx
jle .L3
.L6:
imull %edx, %eax
subl $1, %edx
jne .L6
.L3:
popl %ebp
ret
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 13 grudnia 2012 56 / 60
Jeszcze jeden przykład wywołan koncowych
int even(int n)
{
if(!n) return 1; else return odd(n-1);
}
int odd(int n)
{
if(n==1) return 1; else return even(n-1);
}
W tym wypadku mamy do czynienia z wywołaniami koncowymi,które trudno zoptymalizowac na JVM.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 13 grudnia 2012 57 / 60
gcc -O1
even:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
movl 8(%ebp), %edx
movl $1, %eax
testl %edx, %edx
je .L9
leal -1(%edx), %eax
movl %eax, (%esp)
call odd
.L9:
leave
ret
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 13 grudnia 2012 58 / 60
gcc -O1 -foptimize-sibling-calls
even:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
testl %eax, %eax
je .L12
subl $1, %eax
movl %eax, 8(%ebp)
popl %ebp
jmp odd
.L12:
movl $1, %eax
popl %ebp
ret
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 13 grudnia 2012 59 / 60
Wyjatki
Pojecie wyjatek oznacza bład (nietypowa, niepozadana sytuacje).
Obsługa wyjatków oznacza reakcje programu na wykryte błedy.
Funkcja, która napotkała problem zgłasza (rzuca) wyjatek.
Wyjatek jest przekazywany do miejsca wywołania funkcji, gdziemoze byc wyłapany i obsłuzony albo przekazany wyzej. Innymisłowy poszukiwania bloku obsługi wyjatku dokonywane sa połancuchu DL.
Przy wychodzeniu z funkcji i bloków moze zaistniec potrzebazwolnienia zaalokowanych w nich obiektów (np. wywołaniadestruktorów).
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 10 stycznia 2013 2 / 33
Semantyka
Gdy któras z instrukcji w czesci try przekazała wyjatek,przerywamy wykonanie tego ciagu i szukamy catch zodpowiednim parametrem.
Jesli znajdziemy, to wykonujemy obsługe tego wyjatku, a po jejzakonczeniu instrukcje po wszystkich blokach catch.
Jesli nie znajdziemy, przechodzimy do miejsca wywołania(usuwajac obiekty automatyczne biezacej funkcji) i kontynuujemyposzukiwanie.
Jesli nie znajdziemy w zadnej z aktywnych funkcji, wykonanieprogramu zostanie przerwane.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 10 stycznia 2013 4 / 33
Postepowanie w razie wyjatku
W momencie zgłoszenia wyjatku musza zostac wykonane nastepujaceczynnosci:
stwierdzenie, czy nastapiło ono wewnatrz bloku try,
identyfikacja aktywnych bloków try — moze byc wiecej niz jeden,
rozpoznanie typu zgłoszonego wyjatku,
próba dopasowania do typu wyjatku jednego z bloków catch,
w wypadku powodzenia wykonanie tego bloku,
w przeciwnym wypadku przekazanie wyjatku w góre DL .
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 10 stycznia 2013 6 / 33
Identyfikacja aktywnych bloków try
W czasie wykonania programu musi byc dostepna informacja(struktura danych), która dla kazdej instrukcji pozwoli ustalic czyi jakie bloki try ja otaczaja.
Jezeli chcemy uniknac narzutu dla ’prawidłowego’ przebieguprogramu, informacja taka musi byc w całosci wygenerowana wczasie kompilacji.
Powszechnie stosowana metoda jest uzycie tablicy indeksowanejadresami (zakresami adresów) instrukcji.
Elementami tej tablicy beda listy odpowiednich bloków try lublisty odpowiednich bloków catch.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 10 stycznia 2013 7 / 33
Przykład c.d.
Załózmy przy tym, ze wygenerowany dla niej został nastepujacy kodmaszynowy:
0: enter
1: call f
2: call g
3: jmp 7
4: call i1
5: jmp 7
6: call i2
7: call i3
8: leave
9: ret
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 10 stycznia 2013 9 / 33
Przykład c.d.
Tablica, o której mowa wygladac bedzie nastepujaco
Od Do Bloki catch
1 1 C2
2 2 C1, C2
3 5 C2
Ponadto dla kazdego bloku catch potrzebujemy informacji o typieobsługiwanego wyjatku oraz adresie jego kodu:
Catch Typ Adres
C1 E1 4
C2 E2 6
Tablice te moga łatwo zostac wygenerowane w czasie kompilacji.Uzbrojeni w nie, mozemy przejsc do nastepnego etapu: dopasowaniabloku catch do typu wyjatku
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 10 stycznia 2013 11 / 33
Dopasowanie bloku catch do typu wyjatku
W wielu jezykach wyjatkiem moze byc dowolna wartosc (obiekt).Dla kazdego obiektu musi zatem istniec mozliwosc stwierdzenia wczasie wykonania, czy jest on okreslonego typu.
Jezyki z wyjatkami zwykle udostepniaja informacje o typach w czasiewykonania (ang. Run Time Type Information, RTTI)
Rozwazmy nasz przykład poszerzony o nastepujace definicje:
class E1 {};
class E2 {};
class E3 : public E2 {};
class K {};
void g() {
K k;
throw (new E3());
}
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 10 stycznia 2013 12 / 33
Dopasowanie bloku catch do typu wyjatku
Catch Typ Adres
C1 E1 4
C2 E2 6
Wywołanie funkcji g powoduje zgłoszenie wyjatku. Nazwijmy jegowartosc e.W poprzedniej fazie ustalilismy, ze aktywne sa bloki C1, C2.Przystepujemy zatem do dopasowania typów:
C1 obsługuje typ E1; czy e jest typu E1? NIE.
C2 obsługuje typ E2; czy e jest typu E2? TAK(jest klasy E3, która jest podklasa E2).
Wykonany powinien zostac blok C2, czyli skok pod adres 6.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 10 stycznia 2013 13 / 33
Zwijanie stosu
Jesli nie został odnaleziony zaden pasujacy do typu wyjatku blokcatch (w szczególnosci, jesli nie bylismy w zadnym bloku try),kontynuujemy poszukiwanie wzdłuz łancucha DL, usuwajac podrodze wszystkie obiekty automatyczne.
W naszym przykładzie:
void g(){
K k;
throw (new E3());
}
nalezy usunac obiekt k i kontynuowac poszukiwania w miejscuwywołania funkcji g (czyli w funkcji h).
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 10 stycznia 2013 14 / 33
Proste zwijanie stosu
Najprostsza metoda realizacji takiego zachowania jest ustawienie flagioznaczajacej wyjatek, a potem zachowanie takie, jak przy powrocie zfunkcji.
Kod dla wywołania funkcji musi po powrocie sprawdzic flage wyjatkui w razie potrzeby podjac poszukiwania bloku obsługi dla tegowyjatku.
Rozwiazanie to wprowadza pewien dodatkowy koszt takze wsytuacjach, kiedy nie został zgłoszony zaden wyjatek (flage wyjatkutrzeba sprawdzac po kazdym wywołaniu funkcji).
Koszt ten jest jednak dosc niewielki (1-2 instrukcje procesora nawywołanie).
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 10 stycznia 2013 15 / 33
Pełne zwijanie stosu
Jezeli chcemy uniknac tego kosztu, musimy zaimplementowac pełnezwijanie stosu.
W tym celu musimy przechowywac (poza stosem maszynowym) listeobiektów automatycznych.
Przy zgłoszeniu wyjatku poszukujemy po łancuchu DL ramki stosuzawierajacej odpowiedni blok catch (patrzymy na slad powrotu) poczym usuwamy kolejno wszystkie obiekty az do tej ramki.
Pewnej starannosci wymaga rozstrzygniecie, które obiekty z tejostatniej ramki powinny zostac usuniete.
Oczywiscie sprawa jest prostsza w jezykach z automatycznymzarzadzaniem pamiecia (odsmiecaniem).
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 10 stycznia 2013 16 / 33
Zwijanie stosu a wskaznik ramki
Powyzszy algorytm (przejscie po łancuchu DL) zakłada istnieniełancucha DL i wskaznika ramki
Co zrobic gdy protokół wywołania pomija wskaznik ramki? (np.-fomit-frame-pointer)
Mozemy odtworzyc łancuch DL przy pomocy wskaznika stosui sladów powrotu.
W kazdym punkcie kodu musimy wiedziec jak głeboko lezy sladpowrotu
Zwijamy stos zgodnie z wytycznymi biezacej funkcji i przechodzimydo poprzedniej ramki
Slad powrotu wskazuje wytyczne dla poprzedniej ramki.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 10 stycznia 2013 17 / 33
Zarzadzanie pamiecia
Przydział pamieci◮ lista wolnych bloków; znajdowanie bloku o odpowiednim
rozmiarze◮ fragmentacja wolnej pamieci◮ kompaktyfikacja◮ buddy-systems
Zwalnianie pamieci◮ jawne (np. C)◮ automatyczne (Python, Smalltalk, Java, .NET)◮ odsmiecanie (garbage collection).
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 10 stycznia 2013 19 / 33
Przydział pamieci a informacje administracyjne
Czesto musimy przydzielic wiekszy blok niz zamówiono
Przydzielony blok musi przechowywac pewne informacjeadministracyjne, np.
rozmiar
łacze na liscie zajetych/wolnych bloków
licznik odwołan (dla potrzeb odsmiecania)
Taki nagłówek ma zwykle stały rozmiar, powiedzmy h.
Uzyteczna sztuczka: przydzielamy blok pod adresem a, na poczatkuumieszczamy nagłówek, do programu przekazujemy adres b = a+ h.
Nagłówek bloku b jest pod adresem b− h.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 10 stycznia 2013 20 / 33
Jawne zwalnianie pamieci
Zalety:
Prosta implementacja
Dobrze okreslony moment wywołania destruktora (wazne jeslima zwalniac inne zasoby np. zamykac pliki czy połaczenia)
Wady:
Wycieki pamieci
Trudne do wykrycia błedy
Dodatkowy koszt programowania
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 10 stycznia 2013 21 / 33
Odsmiecanie
Nieuzywane (niedostepne) bloki pamieci musza byc rozpoznanei zwolnione.
Podstawowe metody:
Zliczanie odwołan (reference counting)
Metody sledcze (tropia dostepne bloki):◮ Kopiowanie◮ Mark-sweep
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 10 stycznia 2013 22 / 33
Synchronizacja
Inny wazny podział metod odsmiecania:
synchroniczne (zatrzymajcie swiat, ja odsmiecam)
asynchroniczne (równolegle z działajacym programem)
Metody synchroniczne zatrzymuja program na czas odsmiecania —w najlepszym wypadku dyskomfort uzytkownika.Metody asynchroniczne sa zas trudne w implementacji (i zwyklemniej skuteczne).
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 10 stycznia 2013 23 / 33
Konserwatywnosc odsmiecania
Z punktu widzenia poprawnosci algorytm odsmiecania musizagwarantowac, ze nigdy nie usunie dostepnego obiektu.
Idealny odsmiecacz usuwa wszystkie niedostepne obiektynatychmiast gdy staja sie niedostepne.
Realne odsmiecacze nie usuwaja wszystkich smieci, przynajmniejnie od razu.
Z tej przyczyny mówimy o konserwatywnosci odsmiecaczy(zachowuja niektóre smieci) i jej stopniach (jeden algorytmzachowuje wiecej smieci niz drugi).
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 10 stycznia 2013 24 / 33
Zliczanie odwołan
Kazdy obiekt przechowuje licznik wskazników, które donprowadza.
Kazde przypisanie wskaznika modyfikuje odpowiednie liczniki;gdy licznik dojdzie do 0 — zwalniamy obiekt.
Zalety:◮ prosta w implementacji metoda asynchroniczna.◮ dobrze okreslony moment wywoływania finalizatorów
Wady:◮ narzut czasowy i pamieciowy◮ niezwalnianie niedostepnych cykli (np. listy dwukierunkowe).
Czasem stosowane w połaczeniu z innymi metodami.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 10 stycznia 2013 25 / 33
Odsmiecanie sledcze
Poczynajac od zbioru korzeni (np stos, zmienne globalne) sledzimyktóre obiekty sa (a raczej moga byc) uzywane. Pozostałe sa smieciami.Problemy:
Wybór korzeni (które komórki stosu? rejestry?)
Rozpoznawanie wskazników
Narzuty czasowe (przejscie całej zaalokowanej pamieci) lubpamieciowe
Lokalnosc (stronicowanie, cache)
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 10 stycznia 2013 26 / 33
Odsmiecanie kopiujace
Dostepna pamiec dzielimy na dwa równe obszary;
w jednym z nich alokujemy nowe obiekty, drugi pusty.
Gdy zabraknie pamieci, wykrywamy dostepne obiekty iprzenosimy je do drugiego obszaru.
Po zakonczeniu przenosin w pierwszym obszarze pozostaja samesmieci, wiec mozemy uznac go za pusty...
...i zamienic obszary rolami.
Cena:
tylko połowa dostepnej pamieci jest rzeczywiscie uzywana;
za to koszt czasowy proporcjonalny do rozmiaru dostepnychobiektów.
dwupoziomowe wskazniki (lub trudne mechanizmy zmianywskazników).
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 10 stycznia 2013 27 / 33
“Zaznacz i zamiec” (mark and sweep)
Odznaczamy wszystkie obiekty
Poczynajac od korzeni, obchodzimy graf dostepnych obiektów
Które rejestry zawieraja wskazniki?
Krawedziami sa wskazniki, jak je wykryc?◮ znaczniki◮ jesli wyglada jak wskaznik, bezpiecznie załozyc ze jest
wskaznikiem (konserwatywnosc)
Wykrywamy dostepne obiekty i zaznaczamy je jako dostepne
Po zakonczeniu zaznaczania, wszystkie niezaznaczone obszary sasmieciami.
Przechodzimy wszystkie obiekty i niezaznaczone usuwamy.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 10 stycznia 2013 28 / 33
“Zaznacz i zamiec” (mark and sweep)
Koszty:
przechowywanie listy wszystkich zaalokowanych obiektów;
znaczniki dostepnosci (w nagłówku lub odrebna tablica)
stos/kolejka dla obejscia grafu (lub odwracanie wskazników)
koszt czasowy proporcjonalny do łacznego rozmiaru pamieci.
Dla skrócenia przerw, odznaczanie i zamiatanie moze wykonywacalokator.
Wariant “ze zgniataniem”: dla unikniecia fragmentacji przesuwamydostepne obiekty do spójnego obszaru.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 10 stycznia 2013 29 / 33
Odsmiecanie generacyjne
Hewitt, 1987:
wiekszosc obiektów umiera młodo
nowe obiekty czesciej zawieraja wskazniki do starszych niz naodwrót
Pomysł:
“Szkółka” dla młodych obiektów z odsmiecaniem kopiujacym(tzw. małe odsmiecanie)
Szkółka jest mała i w wiekszosci zasmiecona (obiekty umierajamłodo)
Gdy mało miejsca w szkółce po odsmiecaniu, przenies obiekty doduzej przestrzeni
Wymaga specjalnego traktowania wskazników od starych do młodychobiektów.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 10 stycznia 2013 30 / 33
Odsmiecanie przyrostowe (współbiezne,asynchroniczne)
Czesto zatrzymanie programu (na blizej nieokreslony czas) dlaprzeprowadzenia odsmiecania nie jest akceptowalne.
Wtedy odsmiecanie musi byc synchronizowane z działaniemprogramu.
Problem: Podczas gdy Odsmiecacz przechodzi graf dostepnychobiektów, Program moze ten graf zmieniac.
Z tej przyczyny z punktu widzenia odsmiecacza, programnazywany jest Zmieniaczem (ang. mutator).
Odsmiecacz tez moze zmieniac fragmenty grafu, których uzywaZmieniacz — konieczna pełna współbieznosc.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 10 stycznia 2013 31 / 33
Trójkolorowe zaznaczanie (Dijkstra, Lamport et al.)
Algorytmy odsmiecania moga byc opisane jako proces obchodzenia ikolorowania grafu obiektów:
obiekty podlegajace odsmiecaniu maja kolor biały
na koncu odsmiecania obiekty dostepne maja miec kolor czarny
Dla synchronizacji Odsmiecacza i Zmieniacza wprowadzamytrzeci kolor: szary
Obiekt jest szary, jesli został juz odwiedzony, ale jego potomkowieniekoniecznie.
Niezmiennik: zaden czarny obiekt nie moze zawierac wskaznikado białego.
Postep obejscia odbywa sie w “szarej strefie” (białe obiekty stajasie szare, szare staja sie czarne).
Gdy nie ma juz szarych obiektów, białe sa smieciami
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 10 stycznia 2013 32 / 33
Synchronizacja odsmiecania — bariery
Bariera odczytu: odczyt wskaznika do białego obiektu powoduje,ze staje sie on szary:
◮ jest dostepny,◮ Zmieniacz nigdy nie dostanie wskaznika do białego obiektu.
Bariera zapisu: rózne mechanizmy zapewniajace zachowanieniezmiennika przy zapisie wskazników, np:
◮ Steele: zapis wskaznika do białego obiektu zmienia go w szary (aleteraz trzeba udowodnic postep algorytmu)
◮ Dijkstra: zapis wskaznika zmienia obiekt wskazywany w czarny(oczywisty postep algorytmu, ale bardziej konserwatywne).
◮ Nowe obiekty moga byc oznaczane jako czarne (bardziejkonserwatywne) lub białe (liczac na to, ze nowe obiekty zyja krócejniz stare).
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 10 stycznia 2013 33 / 33
Specyficzne cechy jezyków funkcyjnych
Jezyki funkcyjne posiadaja pewne specyficzne cechy, które sprawiaja,ze metody ich implementacji róznia sie znaczaco od jezykówimperatywnych.Funkcje sa pełnoprawnymi obywatelami:
1 Moga byc argumentami funkcji
2 Moga byc wynikami funkcji
3 Moga byc czesciowo aplikowane
4 Moga byc tworzone anonimowo
5 Obliczenia moga byc leniwe, tzn. wartosc argumentu jestwyliczana nie w momencie wywołania funkcji, ale w momenciejego (pierwszego) uzycia.
6 Nie ma przypisania (tylko obliczanie wartosci wyrazen); w tzw.czystych jezykach funkcyjnych zasadniczo nie ma w ogóleefektów ubocznych
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 2 / 38
Przykład
Spójrzmy na prosty przykład ilustrujacy te cechy
map :: (a->b) -> [a] -> [b]
map f [] = []
map f (x:xs) = (f x):(map f xs)
increaseAll :: [Int] -> [Int]
increaseAll = map (\x->x+1)
Funkcja map bierze funkcje (a → b) i [a], dajac w wyniku [b].
Inne odczytanie: argumentem jest funkcja typu (a → b), zaswynikiem. . . funkcja typu [a]→ [b].
Funkcja increaseAll korzysta z tego drugiego odczytania, stosujacfunkcje map z jednym argumentem, którym jest anonimowa funkcjadodajaca jeden do swego argumentu.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 3 / 38
Wpływ na implementacje
Ad 1 Funkcje jako argumenty funkcji wystepuja w innych jezykach.Mozemy jednak byc zmuszeni do tworzenia i przekazywaniadomkniecia funkcji dla dostepu do zmiennych nielokalnych.
Ad 2 Jesli funkcja moze dawac w wyniku funkcje, dostep dozmiennych nielokalnych jest problematyczny.
◮ Nie mozemy skorzystac z klasycznego stosu rekordów aktywacjijak w jezykach imperatywnych.
◮ Domkniecie funkcji musi przechowywac wartosci wszystkichzmiennych nielokalnych, z których ta funkcja korzysta.
◮ Mozemy jednak przekształcic program do takiej (równowaznej)postaci, aby zadna funkcja nie korzystała ze zmiennychnielokalnych. Transformacje te przedyskutujemy nieco pózniej.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 4 / 38
Wpływ na implementacje
3–5 W trakcie wykonania programu musimy przechowywacstruktury (grafy) reprezentujace nieobliczone (lub czesciowo tylkoobliczone) wyrazenia.
◮ Wykonanie programu bedzie polegało na konstruowaniu iredukowaniu takich grafów.
◮ Grafy, a nie drzewa zwn. wspólne podwyrazenia.◮ W jezykach leniwych grafy nie musza byc acykliczne.◮ Jak zobaczymy za chwile, podejscie takie jest wygodne takze z
innych powodów.
Ad 6 Brak efektów ubocznych umozliwia stosowanie na szeroka skaletransformacji programów do równowaznej, ale wygodniejszej lubefektywniejszej postaci.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 5 / 38
Domkniecia
Problem zmiennych nielokalnych jest nam juz znany.
Standardowym rozwiazaniem jest reprezentowanie wartoscifunkcyjnej przez tzw. domkniecie — wartosc, z której mozna utworzycwcielenie danej funkcji wszedzie, gdzie moze to byc potrzebne.
Co powinno zawierac takie domkniecie — zalezy od konkretnegojezyka, a nawet od implementacji.
W Pascalu wystarczy gdy domknieciem jest para (adres funkcji, SL)— ramka SL nie zniknie przedwczesnie ze stosu.
Kiedy funkcje moga byc wynikami funkcji, własnosc ta nie moze byczagwarantowana.
Za domkniecie przyjmuje sie wtedy zwykle informacje o wartosciachwszystkich zmiennych wolnych. Ich ilosc (a zatem rozmiardomkniecia) mozna wyznaczyc statycznie.
Jesli sa efekty uboczne — adresy zmiennych zamiast ich wartosci.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 6 / 38
Superkombinatory
Pewne funkcje mozemy bezpiecznie przekazywac bezdodatkowych informacji.
Sa to te, które... nie maja zmiennych wolnych!
Na tej obserwacji bazuje nastepujaca metoda implementacjijezyków funkcyjnych: transformujemy program do postaci, wktórej zadna funkcja nie ma zmiennych wolnych.
Funkcje takie nazywa sie superkombinatorami, a procestransformacji lambda-liftingiem.
Na razie bedziemy zakładac, ze program dany jest w postaci ciagudefinicji superkombinatorów. Domknieciami i lambda-liftingiemzajmiemy sie w dalszej czesci wykładu.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 7 / 38
Grafy wyrazenGraf wyrazenia jest uogólnieniem drzewa wyrazenia. Rozwazmy naprzykład funkcje:kwadrat x = x * x
Mozemy ja przedstawic w postaci drzewa
*
x x
albo grafu
*
x
Poniewaz wyrazenie x*x oznacza zastosowanie funkcji * doargumentów x oraz x, dokładniejsza bedzie reprezentacja:
@
@
*
x
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 8 / 38
Redukcje grafów
Wykonanie programu funkcyjnego polega na obliczeniu wartosciwyrazenia poprzez kolejne redukcje.Przy reprezentacji grafowej redukcje te dokonywane sa na grafachwyrazen.Przykład:
kwadrat x = x * x ;
main = kwadrat (kwadrat 3)
Redukcje beda przebiegac nastepujaco
main 7→
@
kwadrat @
kwadrat 3
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 9 / 38
Redukcje grafów
Zewnetrzna aplikacje funkcji kwadrat zastepujemy jej grafem,zastepujac w nim x grafem argumentu:
@!
kwadrat @
kwadrat 3
7→
@
@
*
@
kwadrat 3
(! oznacza wierzchołek, w którym redukujemy, i który zostaniezastapiony wynikiem redukcji)
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 10 / 38
Redukcje grafów
Mnozenie mozna wykonac tylko na liczbach, musimy wieczredukowac argumenty *:
@
@
*
@ !
kwadrat 3
7→
@
@
*
@
@
*
3
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 11 / 38
Redukcje grafów
Teraz jedyna mozliwa redukcja jest wewnetrzne mnozenie:
@
@
*
@ !
@
*
3
7→
@
@
*
9
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 12 / 38
Redukcje grafów
Ostatnia redukcja jest juz bardzo prosta:
@!
@
*
9
7→ 81
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 13 / 38
Metoda szablonów
Metoda szablonów, choc rzadko stosowana bezposrednio (z uwagi naswa niska efektywnosc) lezy u podstaw wielu metod implementacjijezyków funkcyjnych.
dla kazdej funkcji tworzymy jej szablon (graf ciała funkcji, zwolnymi "gniazdkami"dla argumentów)
w momencie wywołania funkcji (tj. redukcji aplikacji) tworzymywcielenie (kopie) danego szablonu z parametrami faktycznymi“właczonymi” w odpowiednie gniazdka argumentów szablonu.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 14 / 38
Przykłady szablonów
Szablon dla funkcji main = kwadrat(kwadrat 3)
@
kwadrat @
kwadrat 3
(funkcja main nie ma argumentów)
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 15 / 38
Przykłady szablonów
Szablon dla funkcji kwadrat x = x * x
@
@
* A1
A1 lub
@
@
*
A1
gdzie A1 oznacza gniazdko dla pierwszego argumentu
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 16 / 38
Nieefektywnosc metody szablonów
Zastosowanie tej metody prowadzi do stworzenia raczejinterpretera niz kompilatora: nie generujemy kodu w sensie ciaguinstrukcji, a jedynie grafy funkcji.
Cały proces redukcji jest realizowany przez system wykonawczy.Stad bierze sie wspomniana nieefektywnosc metody szablonów.
Mozna usprawnic ten proces generujac kod, który zbuduje grafdla funkcji. Na tym pomysle oparta jest G-maszyna autorstwaAugustssona i Johnsona.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 17 / 38
Znajdowanie nastepnego redeksu
Zajmiemy sie jedynie strategia lewostronna, która ma te wazna ceche,ze jesli tylko istnieje jakakolwiek kolejnosc redukcji, która doprowadzido obliczenia wyrazenia, to strategia lewostronna bedzie miała takisam efekt.Strategia lewostronna polega na znalezieniu redeksu lezacego“najbardziej na lewo” w drzewie wyrazenia. Mozemy ja zrealizowacnastepujaco:
1 Zaczynajac od korzenia, podazamy lewa gałezia drzewa az donapotkania superkombinatora. Zwykle w trakcie tego procesuzapamietujemy odwiedzone wierzchołki na stosie. Proces tennazywany jest rozwijaniem grzbietu, a jego slad na stosie grzbietem.
2 Sprawdzamy ilosc argumentów superkombinatora i cofamy sie otaka ilosc kroków w góre drzewa. Napotkany wezeł jestnastepnym redeksem.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 18 / 38
G-maszynaG-maszyna (czyli maszyna grafowa) składa sie ze stertyprzechowujacej grafy, oraz stosu, na którym przechowywane sawskazniki do sterty oraz stałe.
W momencie wywołania funkcji na stosie lezy grzbiet(albternatywnie: wskazniki do argumentów i redeksu).
Kod funkcji buduje graf dla wcielenia funkcji i dokonuje kolejnejredukcji.
Sercem G-maszyny sa instrukcje zwiazane z budowaniem grafów i ichredukcja. Do tej kategorii naleza m.in. instrukcje:
PUSH n — połóz na wierzchołku stosu n-ty (wzgledem biezacegowierzchołka stosu) argument
PUSHGLOBAL f — połóz na stosie adres funkcji f
PUSHINT n — połóz na stosie stała całkowita n
MKAP — zbuduj wezeł aplikacji (uzywajac dwóch pierwszychelementów stosu)
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 19 / 38
G-maszyna
SLIDE n — usun n elementów spod wierzchołka stosu. Jesli stosprzed ta operacja zawiera elementy
a0;a1; . . . ;an;b;c . . .
to po tej operacji bedzie zawierał
a0;b;c . . .
UNWIND — znajdz nastepny redeks (rozwijajac grzbiet)i zredukuj go (skaczac do kodu odpowiedniegosuperkombinatora).
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 20 / 38
Przykłady
Kod dla naszej przykładowej funkcji main bedzie zatem wygladałnastepujaco:
PUSHINT 3
PUSHGLOBAL kwadrat
MKAP
PUSHGLOBAL kwadrat
MKAP
SLIDE 1
UNWIND
[animacja na tablicy]
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 21 / 38
Przykłady
Zas dla funkcji kwadrat
PUSH 0 (ARG1)
PUSH 1 (tez ARG1, ale stos wzrósł o 1)
PUSHGLOBAL *MKAP
MKAP
SLIDE 2
UNWIND
Zauwazmy, ze graf budowany przez powyzszy kod nie jest drzewem -zawiera dwie krawedzie prowadzace do argumentu funkcji.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 22 / 38
Generacja kodu
Dla funkcji n-argumentowej schemat generacji kodu wygladanastepujaco:
instrukcje budujace graf (łatwo je wygenerowac obchodzacszablon funkcji w porzadku postfiksowym)
SLIDE n+1 (usuwanie ze stosu starego grzbietu przy zachowaniuwskaznika do nowozbudowanego grafu)
UNWIND (rozwiniecie grzbietu i redukcja znalezionego redeksu).
Poza podstawowymi instrukcjami G-maszyna posiada instrukcjerealizujace operacje wbudowane (np. arytmetyczne) oraz inneoperacje charakterystyczne dla kompilowanego jezyka.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 23 / 38
Schematy generacji koduSchemat F generuje kod dla superkombinatora
F [[ f x1 . . .xn = e]] = R[[e]] ρf n
ρf = [x1 7→ 0, . . . ,xn 7→ n− 1]
Schemat R tworzy kod, który buduje graf ciała i redukuje go:
R[[e]] ρ d = C [[e]] ρ++ [SLIDE d+ 1, UNWIND]
Schemat C generuje kod, który buduje graf wyrazenia:
C [[x]] ρ= [PUSH ρ(x)]
C [[ f ]] ρ= [PUSHGLOBAL f] (f < ρ)
C [[ i]] ρ= [PUSHINT i]
C [[e0e1]] ρ= C [[e1]] ρ++C [[e0]] ρ+1 ++ [MKAP]
gdzie ρ+n(x) = ρ(x)+ n.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 24 / 38
Leniwe obliczenia
Zauwazmy, ze opisana powyzej maszyna nie realizuje w pełniparadygmatu leniwych obliczen: co prawda argumenty sa obliczanedopiero kiedy potrzeba, moga jednak byc obliczne wielokrotnie. Abytemu zaradzic (i zwiekszyc sprawnosc maszyny) musimy wprowadzicjeszcze dwie operacje (zamiast SLIDE):
UPDATE n — zastap wierzchołek grafu wskazywany przez(n+ 1)-szy element stosu przez wierzchołek na szczycie stosu;Uwaga: to zmienia graf, nie stos!
POP n — zdejmij n elementów ze stosu.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 25 / 38
Leniwe obliczenia
W tej wersji epilog funkcji n-argumentowej wyglada nastepujaco:
UPDATE n
POP n
UNWIND
Dokładniej, musimy zmienic schemat R:
R[[e]] ρ n = C [[e]] ρ++ [UPDATE n, POP n, UNWIND]
[przykład na tablicy]
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 26 / 38
Operacje wbudowane
Wrócmy na chwile do naszego przykładukwadrat(kwadrat 3):
@
@
*
@ !
kwadrat 3
Jak zauwazylismy, mnozenie mozemy wykonac tylko na liczbach,wiec najpierw musimy zredukowac jego argumenty. Ale naszamaszyna tego nie uwzglednia. . .
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 27 / 38
Operacje wbudowane
Musimy wprowadzic nowa instrukcje — EVAL, która:
zapamieta biezaca kontynuacje (kontekst) obliczen — wskaznikkodu i stosu,
obliczy wartosc wyrazenia na szczycie stosu,
powróci do zapamietanej kontynuacji.
Kontynuacje odkładane sa na dodatkowym (meta-)stosie, zwanymskładem (dump).
Musimy tez zmodyfikowac instrukcje UNWIND, tak aby w wypadkunapotkania stałej całkowitej wyjmowała zachowany kontekstze składu, kładac na wierzchu te stała — czyli zachowywała siepodobnie do ireturn w JVM.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 28 / 38
Operacje wbudowane — generacja kodu
Wprowadzamy nowy schemat kompilacji: E, który generuje kodobliczajacy wyrazenie do postaci normalnej (WHNF):
E [[i]] ρ= [PUSHINT i]
E [[e0 ∗ e1]] ρ= E [[e1]] ρ++E [[e0]] ρ+1 ++ [MUL]
E [[e]] ρ= C [[e]] ρ++ [EVAL]
Zauwazmy tez, ze do kodu superkombinatora wchodzimy zawsze zintencja zredukowania go, zatem schemat R zmodyfikujemynastepujaco:
R[[e]] ρ d = E [[e]] ρ++ [UPDATE d, POP d, UNWIND]
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 29 / 38
Przykład
Kod dla funkcji kwadrat x = x * x
PUSH 0
EVAL
PUSH 1
EVAL
MUL
UPDATE 1
POP 1
UNWIND
(drugi EVAL jest redundantny — sprytniejszy generator kodu mógłbyto wykryc)
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 30 / 38
Obliczenia leniwe i (nad)gorliwe
W strategii lewostronnej obliczenia wykonywane sa dopiero wtedygdy sa potrzebne. Taki model nazywamy leniwym (lazy); jest onuzywany m.in. w Haskellu. Na przykład
const x y = x
main = const 0 (1/0)
obliczy sie w tym modelu poprawnie, gdyz 1/0 nie zostanie nigdyobliczone.
Z kolei w jezykach rodziny ML uzywany jest model gorliwy (eager) —argumenty obliczane sa przed wywołaniem funkcji.
Mozliwe jest mieszanie tych dwóch modeli i obliczanie jednychwyrazen w kontekscie gorliwym (schemat E), innych zas w leniwym(schemat C).
Operacje wbudowane (np. +) sa zwykle gorliwe.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 31 / 38
Struktury danychDla reprezentacji algebraicznych struktur danych (jak listy),wprowadzamy nowy rodzaj wezła —konstruktor:
Cons(tag,args . . .)
na przykład dla list[ ] = Cons(0)
(x : xs) = Cons(1,x,xs)
oraz instrukcje:
PACK t n — zdejmuje n elementów ze stosu i opakowuje zetykieta t.
SPLIT — szczyt stosu a wskazuje na Cons(a1, . . . ,an), zastepujemygo przez a1, . . . ,an.
CASEJUMP [t1 -> kod1,...] — skacze do koduodpowiadajacego etykiecie konstruktora wskazywanego przezszczyt stosu.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 32 / 38
Przykład CASEJUMP
length xs = case xs of {
[] -> 0; (:) y ys -> 1 + length ys }
PUSH 0
EVAL
CASEJUMP [
0 -> [PUSHINT 0]
1 -> [ SPLIT 2, PUSHGLOBAL length, MKAP
EVAL, PUSHINT 1, ADD, SLIDE 2]]
UPDATE 1
POP 1
UNWIND
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 33 / 38
Schematy generacji kodu
Niech Ct,n oznacza n-argumentowy konstruktor o etykiecie t.
E [[case e of alts]]ρ= E [[e]]ρ++CASEJUMP D [[alts]]ρ
D [[alt1, . . . ,altn]]ρ= [A [[alt1]]ρ, . . . ,A [[altn]]ρ]
A [[Ct,n x1 . . .xn → e]]ρ= t → [SPLIT n] ++E [[e]]ρ ′++ [SLIDE n]
ρ ′ = ρ+n[x1 7→ 0. . .xn 7→ n− 1]
Kontruktory sa domyslnie leniwe (a C~e jest w WHNF):
E [[Ct,n e1 . . .en]] = C [[en]]ρ+0 ++ . . .C [[e1]]ρ
n−1 ++ [PACK t n]
Trzeba tylko zapewnic, ze konstruktory sa zawsze nasycone, tzn nie saczesciowo aplikowane — łatwe, dodajemy odpowiednie λ-abstrakcje.Podobnie dla case w leniwym kontekscie.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 34 / 38
Inne maszyny
Zauwazmy, ze G-maszyna poswieca duzo czasu na operacjerozwijania grzbietu.
Pojedyncza instrukcja UNWIND reprezentuje w istocie dosczłozona operacje.
Niektóre nowsze maszyny wirtualne (takie jak Three InstructionMachine (TIM) [Fairbairn,Wray] czy Spineless Tagless G-machine(STG) [Peyton Jones] zastepuja operacje rozwijania grzbietu przezefektywniejsze (choc bardziej skomplikowane) mechanizmy.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 35 / 38
Lambda-lifting
Lambda-lifting jest transformacja programu do postacisuperkombinatorów czyli funkcji, które nie korzystaja za zmiennychnielokalnych, a jedynie ze swoich argumentów. Mozemy to osiagnacuzywajac dwóch operacji:
przydawanie funkcji dodatkowych argumentów przekazujacychwartosci zmiennych nielokalnych;
podnoszenie (lifting) funkcji lokalnych na poziom globalny.
Na przykład funkcja
f xs y = map (\z -> h z y) xs
zostanie przetransformowana do zbioru superkombinatorów:
f xs y = map (g y) xs
g y z = h z y
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 36 / 38
Lambda-lifting
Transformacja ta realizowana jest nastepujaco:
dla kazdej funkcji lokalnej obliczamy jej zbiór zmiennych wolnych(nielokalnych);
tworzymy dla niej nowy superkombinator majacy jako argumentypierwotne argumenty funkcji oraz jej zmienne nielokalne;
wystapienie tej funkcji lokalnej zastepujemy odpowiednimzastosowaniem utworzonego superkombinatora.
Marcin Benke (MIM UW) Metody Realizacji Jezyków Programowania 17 stycznia 2013 37 / 38