Co robi kompilator? Analiza - LK - Wiadomościstudents.mimuw.edu.pl/~dm292680/mrjp1.pdf · Co to...

27
Co to jest kompilator? Program który tłumaczy programy w j˛ ezyku wy˙ zszego 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 znajomo´ sci maszyny docelowej, stworzenie kompilatora wymaga dogł˛ ebnej znajomo´ sci maszyny docelowej. Marcin Benke (MIM UW) Metody Realizacji J ˛ ezyków Programowania 1 pa´ zdziernika 2012 2 / 36 Co robi kompilator? Wczytuje program, zwykle jako tekst. Sprawdza poprawno´ c i dokonuje analizy programu. “My´ sli” chwil ˛ e (dokonuje szeregu transformacji programu). Generuje kod wynikowy (synteza). Cz˛ sci kompilatora realizuj ˛ ace analiz˛ e i syntez˛ e okre´ sla si ˛ e czasem nazwami front-end i back-end Istnieje wiele podobnych klas problemów/programów, gdzie analizujemy dane wej´ sciowe (zwykle tekst) tłumaczymy na inny “j˛ ezyk”. Marcin Benke (MIM UW) Metody Realizacji J ˛ ezyków Programowania 1 pa´ zdziernika 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 — powi ˛ azanie u˙ zycia identyfikatorów z odpowiednimi deklaracjami; kontrola typów. Ka˙ zda z faz analizy powinna dawa´ c czytelne komunikaty o napotkanych bł ˛ edach Trudne, ale bardzo wa˙ zne. Faza analizy jest niezale˙ zna od j ˛ ezyka docelowego. Marcin Benke (MIM UW) Metody Realizacji J ˛ ezyków Programowania 1 pa´ zdziernika 2012 4 / 36 Analiza semantyczna Analiza deklaracji Zapis informacji w tablicy symboli Kontrola poprawno´ sci u˙ zycia symboli i powi ˛ azanie z odpowiednimi deklaracjami (poprzez tablic˛ e symboli). Kontrola (lub rekonstrukcja) typów. Marcin Benke (MIM UW) Metody Realizacji J ˛ ezyków Programowania 1 pa´ zdziernika 2012 9 / 36 Maszyny docelowe Fizyczna architektura procesora, np x86, x86_64, SPARC, ARM Maszyna wirtualna stosowa, np. JVM rejestrowa, np. LLVM Maszyna wirtualna mo˙ ze by´ c u˙ zyta jako etap po´ sredni na drodze do kodu maszynowego Ahead of Time (AOT) — generacja kodu maszynowego przed rozpocz ˛ eciem 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 J ˛ ezyków Programowania 1 pa´ zdziernika 2012 10 / 36 Synteza Transformacja drzewa struktury do postaci dogodnej do dalszych przekształce ´ n (kod po´ sredni) 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 J ˛ ezyków Programowania 1 pa´ zdziernika 2012 11 / 36 Generatory analizatorów leksykalnych Pisanie analizatora leksykalnego jest zwykle ˙ zmudne. Dlatego przewa˙ znie jest on generowany automatycznie przez narz˛ edzia takie jak Flex (C,C++), JLex (Java), Alex (Haskell), Ocamllex, C#Lex,. . . Narz˛ edzia takie generuj ˛ a program realizuj ˛ acy automat rozpoznaj ˛ acy leksemy na podstawie opisu zło˙ zonego z wyra˙ ze´ n regularnych i przypisanych im akcji. Marcin Benke (MIM UW) Metody Realizacji J ˛ ezyków Programowania 1 pa´ zdziernika 2012 22 / 36 Analiza syntaktyczna Analizator syntaktyczny (parser) jest funkcj ˛ a sprawdzaj ˛ ac ˛ a, czy dane słowo nale˙ zy do j ˛ ezyka i, je´ sli tak, buduj ˛ ac ˛ a drzewo struktury. Algorytm Youngera: O(n 3 ) i nie daje drzewa struktury. Istniej ˛ a efektywne algorytmy dla pewnych klas gramatyk. Dwa zasadnicze podej´ scia: Top-down: próbujemy sparsowa´ c okre´ slon ˛ a konstrukcj ˛ e (nieterminal); drzewo struktury budowane od korzenia do li´ sci. Bottom-up: W danym napisie znajdujemy mo˙ zliwe konstrukcje; drzewo budowane od li´ sci do korzenia ze znalezionych kawałków. Marcin Benke (MIM UW) Metody Realizacji J ˛ ezyków Programowania 1 pa´ zdziernika 2012 23 / 36 Analiza top-down Schemat analizy top-down mo˙ zem zapisa´ c jako automat: Jeden stan, alfabet stosowy Γ= N T , akceptacja pustym stosem. Na szczycie stosu a T : je´ sli na wej´ sciu a — zdejmij ze stosu, wczytaj nast˛ epny symbol. wpp — bł ˛ ad: oczekiwano a. Na szczycie stosu A N, na wej´ sciu a: wybieramy produkcj ˛ e A α tak ˛ a, ˙ ze a SELECT (A α) na stosie zast ˛ epujemy A przez α Powyzszy automat ogl ˛ ada jeden symbol z wej´ scia, ale łatwo go uogólni´ c na wi ˛ eksz ˛ a ich liczb ˛ e — automat LL(k). Dla automatu deterministycznego, wybór produkcji jest wa˙ zny; zbiór symboli dla których wybieramy produkcj ˛ e A α nazywamy SELECT (A α). Teraz zajmiemy si˛ e obliczaniem tego zbioru. Marcin Benke (MIM UW) Metody Realizacji J ˛ ezyków Programowania 1 pa´ zdziernika 2012 24 / 36

Transcript of Co robi kompilator? Analiza - LK - Wiadomościstudents.mimuw.edu.pl/~dm292680/mrjp1.pdf · Co to...

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