Professional Documents
Culture Documents
Odgovori Na Pitanja
Odgovori Na Pitanja
Odgovori Na Pitanja
Izazovi u
razvoju kompilatora.
(i)
Već pedesetih godina u troškovima razvoja se primećuje da veći udeo ima softver nego hardver.
Kompajler daju mogućnost da se jedan isti kod koristi na više različitih arhitektura.
Prvi kompajler nastaje za Fortran 1957. godine.
(ii)
Za kompajlere koji se koriste za razvoj bezbednosno kritičnog servera može da se traži i da budu
formalno verifikovani (COMPCERT)
Takođe, pored softverskih kompajlera postoje i hardverski, koji služe uglavnom da generišu neku
konfiguraciju hardvera potrebnu za rešavanje nekog problema.
(iii)
1. Front end, vrši leksičku sintaksnu i semantičku analizu. Transformiše ulazni program u IR
2. Middle end, nezavisan od jezika i ciljne arhitekture i vrši optimizacije nad IR
3. Back end, zavisi od ciljne arhitekture. Takođe odgovoran za arhitekturalno specifične
optimizacije. Obuhvata specifične optimizacije i generisanje koda.
3. pitanje. Pretprocesiranje i linkovanje
(i) Pretprocesiranje
• Kompilator ne obrađuje tekst programa koji je napisao programer, već tekst koji je nastao
pretprocesiranjem
• Pretprocesiranjem se izvorni kod organizuje u više ulaznih datoteka.
• Objedinjava izvorni kod različitih datoteka u jedinice prevođenja koje šalje kompilatoru. Na
primer .c datoteke koje sadrže datoteke zaglavlja
• Pretprocesor analizira samo pretprocesorske direktive. Primer su #include i #define u C-u.
(ii) linkovanje
Leksika je podoblast sintakse koja se bavi opisivanjem osnovnih gradivnih elemenata jezika.
U okviru leksike definišu se reči i njihove kategorije. U programskom jeziku reči se nazivaju
lekseme a kategorije tokeni.
Pojedinačni karakteri se grupišu u nedeljive celine lekseme koje predstavljaju osnovne leksičke
elemente i pridružuju im se tokeni koji opisuju leksičke kategorije kojima te lekseme pripadaju.
Tokeni mogu da budu: identifikatori, ključne reči, komentari, bilo šta što smo videli recimo u
lexer.lex fajlovima kad smo programirali.
SA definiše relacije između elemenata jezika, time pružajući strukturne opise ispravnih niski
jezika.
Sintaksa definiše načine na koje pojedinačne reči mogu da kreiraju ispravne "rečenice" jezika.
Sintaksa se bavi samo formom i strukutrom jezika bez bilo kakvih razmatranja značenja
(semantike).
Sintaksička stuktura rečenica ili programa se obično predstavlja preko sintaksnog stabla.
Sintaksom programa se obično bavi deo programskog prevodioca koji se naziva parser
Rezultat njegovog rada je sintaksno stablo koje se isporučuje daljim fazama.
** Sad ovde treba da idu neke forme BNF EBNF i sl. ali kapiram da to ne dolazi uz ovo pitanje.
6. pitanje. Semantička analiza Formalna i neformalna semantika. Semantičke greške. Semantička
upozorenja, primeri.
Postoje različite vrste semantičkih upozorenja, važenja nekih semantičkih pravila se negde mogu
proveriti pre izvršavanja programa a negde to nije moguće. (Recimo deljenje nulom ne bi bilo
moguće uhvatiti pre izvršavanja u nekim slučajevima)
Većina kompilatora prevodi sintaksičko stablo dopunjeno i provereno nakon semantičke analize u
odeđeni međukod. Međukod se nakon toga analizira i optimizuje i na osnovu njega se kasnije gradi
rezultujući asemblerski ili mašinski kod.
IR visokog nivoa:
• Zadržava strukturu programskog jezika visokog nivoa
• Dozvoljava kompilacije na ciljanim mašinama
• Dozvoljava JIT kompilaciju ili interpretaciju.
Najčešći oblik je troadresni kod, gde se javljaju dodele promenljivama tako da sa desne strane
dodele ima najviše jedan operator. Naziv nosi jer se u svakoj instrukciji navode "adrese" najviše dva
operatora i rezultata operacije.
GENERIC -> High GIMPLE -> SSA -> Low GIMPLE -> RTL -> Machine Code
8. Optimizacije medukoda. Uloga i primeri.
Razlozi optimizacije:
• Generisanje IR-a uključuje redundatnost u naš kod.
• Programeri su lenji. (Recimo obično postoji kod u petlji koji ne mora da bude u petlji )
Primeri:
• Constant folding - Konstantni izrazi se mogu izračunavati
• Constant propagation - Izbegava se upotreba promenljivih čija je vrednost konstanta
• Strength reduction - Operacije se menjaju onim za koje se smatra da će se izvršiti brže
• Copy propagation - Izbegava se uvođenje promenljivih koje samo čuvaju vrednosti nekih
postojećih promenljivih.
• Dead code elimination - Izračunavanja vrednosti promenljivih koje se dalje ne koriste se
eliminišu.
• Optimizacija petlji - Jedna od najznačajnijih. Uključuje izbacivanje konstantnih izraza van
tela petlje.
Tokom generisanja koda optimizovani međukod se prevodi u završni asemblerski tj. mašinski kod.
Obično se prvo vrši faza odabira instrukcija, a faza raspoređivanja nekad može ići i pre i posle faze
alokacije registara.
• Ulaz u generator koda je IR izvornog programa zajedno sa tablicama simbola koje služe za
utvrđivanje run-time adresa objekata koji se koriste po imenu u okviru IR-a
• Generatori koda razdvajaju instrukcije u "basic blocks"
• U okviru faza optimizacije i generisanja koda obično postoje višestruki prolazi kroz IR koji
se izvršavaju pre generisanja ciljnog programa.
Izbor instrukcija
Kod generator mora da mapira IR program u sekvencu koda koji može da bude izvršen na ciljanoj
arhitekturi. Kompleksnost mapiranja zavisi od:
• Nivoa apstrakcija / Preciznosti IR-a
◦ Ukoliko je IR visokog nivoa, kod generator moze prevoditi svaku IR isntrukciju u
sekvencu instrukcija, takav kod kasnije verovatno mora da se optimizuje
◦ Ukoliko je IR niskog nivoa, onda se očekuje da takav kod bude efikasan
• Priroda instrukcija arhitekture
◦ Uniformnost i kompletnost skupa instrukcija su bitni faktori
◦ Na nekim mašinama recimo floating point se rešava sa odvojenim registrima (loše)
• Željeni kvalitet generisanog koda
◦ Brzina instrukcija je bitan faktor
◦ Ako nas nije briga za efikasnost ciljanog programa, odabir instrukcija je trivijalan
Izbor registara.
Tokom faze registarske alokacije odeđuju se lokacije na kojima će biti skladištene vrednosti svih
promenljivih koje se javljaju u međukodu. Cilj je da što više promenljivih bude skladišteno u
registrima (zbog brzine pristupa memoriji)
*Dve promenljive mogu biti smeštene u isti registar ako im se životni vek ne preklapa.
Raspoređivanje instrukcija
Faza raspoređivanja instrukcija pokušava da doprinese brzini izvršavanja tako što promeni redosled
instrukcija u programu. Na primer, neki rasporedi instrukcija zahtevaju manji broj registara za
privremene rezultate.
Savremeni procesori mogu da izvrše više instrukcija tokom jednog otkucaja sistemskog sata.
Primer: Dok se jedna instrukcija dovlači iz memorije u procesor, druga se dekodira,
treća se već izvršava, a četvrtoj se već skladište rezultati.
11. Jednoprolazni i višeprolazni kompilatori
Neki kompajleri kombinuju skeniranje, pasrsiranje, semantičku analizu i generisanje koda u jednom
prolazu. Takvi kompajleri se nazivaju jednoprolazni kompajleri.
Većina kompajler ipak prolazi kroz kod više puta i to su višeprolazni komapjleri.
• Projekat llvm sastoji se iz biblioteka i alata koji zajedno čine veliku komajlersku
infrastrukturu.
• Započet je kao istraživački rad na Univerzitetu Ilionis 2000. godine, sa ciljem proučavanja
tehnika kompajliranja i kompajlerskih optimizacija.
• Ideja: skup modularnih i ponovno iskoristivih kompajlerskih tehnologija, čiji je cilj podrška
statičkoj i dinamičkoj kompilaciji proizvoljnih programskih jezika.
Od prve verzije 2003. godine llvm je prerastao u projekat koji se sastoji iz velikog broja
podprojekata koji se koriste u:
• Industriji, na primer Apple, ARM, Nvidia...
• Akademiji, samo na matfu :P
LLVM je sveobuhvatni naziv više projekata koji zajedno čine potpun kompajler:
• Prednji deo
• sredisnji deo
• zadnji deo
• optimizatore
• asemblere
• linkere
• i druge komponente...
(ii) Mogućnosti
Na primer, korisnik može:
• da kreira svoj front-end, i da ga poveže na LLVM, koji će mu dodati middle-end i back-end
• za izabran postojeći front-end/back-end da isprobava promene na nivou middle-end-a
• za novu arhitekturu obezbedi back-end, i da koristi Clang i postojeci middle-end
Clang
• Clang je LLVMov C/C++/Objective-c kompilator, koji ima za cilj
◦ Efikasnu kompilaciju
◦ Veoma detaljnu dijagnostiku
◦ Obezbeđivanje platforme za izgradnju različitih alata koji rade na nivou izvornog koda.
Predstavlja prevođenje i analizu izvornog koda programa zapisanih u višim programskim jezicima u
LLVM-ovu međureprezentaciju.
Prevođenje obuhvata leksičku, sintaksičku i semantičku analizu i završava se fazom generisanja
llvom-og međukoda.
Clang je frontend za C jezike. Frontendi za ostale jezike nisu deo LLVMa ali postoji podrška za
značajnije jezike. (Haskell, JavaScript itd.. itd..)
Prve verzije llvma su kao frontend koristile gcc, odnosno modifikovanu verziju llvm-gcc. Da bi se
koristio ovaj alat bilo je potrebno skinuti i izgraditi ceo modifikovan gcc. Zahtevno i komplikovano.
Nakon toga je pokušano sa projektom Dragon Egg koji je bio dodatak za gcc. Nije bilo potrebno
skidati i izgrađivati llvm-gcc
Clang:
• Memorijski efikasan, ispisuje jasne poruke o upozorenjima i greškama
• Izražajna dijagnostika
• Pruža čist API tako da se on može koristiti i u drugim projektima.
Središnji deo obuhvata skup mašinski nezavisnih optimizacija koje se izvršavaju nad LLVMovom
međureprezentacijom.
LLVMova međureprezentacija predstavlja sponu između prednjeg i zadnjeg dela komajlera.
Dizajn LLVM IR je bio od posebnog značaja za uspeh projekta.
LLVMov međukod može biti predstavljen pomoću narednih, međusobno ekvivalentnih formi:
• memorijska reprezentacija - implicitna forma međukoda
• bitkod reprezentacija - smeštena u datotekama *.bc
• pseudo-asemblerska reprezentacija - lako čitljiva, smeštena u *.ll datotekama
file.c -> CLANG -> file.bc -> OPT -> file.bc -> llc -> file.s
Alat opt vrši analizu i optimizaciju koda. Kao ulazni fajl očekuje IR kod i onda pokreće specifičnu
optimizaciju ili vrši analizu koda, u zavisnosti od ulaznih opcija.
Ukoliko opcija -analyze nije zadata, opt pokušava da proizvede optimizovani izlazni kod.
Postoje razne vrste optimizacija.
• Svaka optimizacija je zadata u vidu prolaza kroz kod. Moguće je menjati redosled
optimizacija.
• Da bi se pokrenula neka konkretna optimizacija navodi se ime odgovarajućeg prolaza.
• Redosled pojavljivanja prolaza u komandnoj liniji određuje redosled izvršavanja
• Neke optimizacije zahtevaju da su se pre njih odradile posebne analize i da su prikupljeni
potrebni podaci.
Postoje prolazi koji su bitni za dobijanje raznih informacija neophodnih za neke druge prolaze. Ovi
prolazi se zovu Utility passes.
Validnost vs korektnost (Validan je ako nema grešaka, korektan je ako radi ono što treba da radi)
Isto ime u programu može da referiše na više različitih stvari. (ne promenljivih jer nije isključivo)
Doseg nekog objekta je skup lokacija u programu gde se može referisati na taj objekat preko
uvedenog imena.
Uvođenje nove promenljive u doseg može da sakrije ime neke prethodne promenljive.
19. Doseg i tabela simbola u OOP. Odredivanje dosega kod nasledivanja. Razrešavanje
višeznačnosti
(ii) Kod nasleđivanja potrebno je održavati još jednu tabelu pokazivača koja pokazuje na stek
dosega.
Kada se traži vrednost u okviru specifičnog dosega počinje se sa pretragom u tom konkretnom
dosegu.
Neki jezici kao C++ omogućavaju skakanje do proizvoljne bazne klase ( primer " cout << A::x " )
Neki jezici koriste dinamičko određivanje dosega, koje se sprovodi u fazi izvršavanja: ime odgovara
varijabli sa tim imenom koja je najbliže ugnježdena u fazi izvršavanja.
** nisam siguran na šta se odnosi Odeđivanje dosega, da li na generalno određivanje ili je to ovo što
je ovde napisano.
Greška u radu sa tipovima se javlja kada se primenjuje operacija nad vrednostima koje ne
podržavaju tu operaciju.
Dinamičko određivanje tipova olakšava pisanje prototipova ali ima više grešaka.
Pravila koja definišu šta je dozvoljena operacija nad tipovima formiraju sistem tipova.
U jako tipiziranim jezicima svi tipovi moraju da se poklapaju.
U slabo tipiziranim jezicima mogu se desiti greške u tipovima u fazi izvršavanja.
Jako tipizirani jezici su robusni, ali slabo tipizirani jezici su obično brži.
Zaključivanje tipova:
• Dva osnovna koraka statičkog zaključivanja:
◦ Zaključivanje tipa za svaki izraz na osnovu komponenti izraza
◦ Potvrđivanje da tipovi izraza u određenom kontekstu se poklapaju sa očekivanim
◦ Ovi koraci se obično kombinuju u jednom prolazu
Osobine nasleđivanja:
• Svaki tip nasleđuje samog sebe.
• Ako A nasleđuje B i B nasleđuje C onda i A nasleđuje C
• Ako A nasleđuje B i B nasleđuje A onda su A i B istog tipa
Osobine konvertovanja:
• Ako A nasleđuje B, onda se objekat klase A može konvertovati u objekat klase B
• Svaki tip se može konvertovati u samog sebe
• Ako se A može konvertovati u B i B u C onda može i A u C.
• Ako se A može konvertovati u B i B u A onda su A i B istog tipa
Ako je A osnovni tip ili niz onda se on može konvertovati samo u samog sebe.
Definišemo novi tip koji odgovara literalu null i zovemo ga null tip.
Definišemo da važi null tip <= A, za svaki klasni tip A
Ovaj tip obično nije dostupan programerima već se koristi samo interno u sistemu tipova
Mnogi programski jezici imaju ovakav tip u svom sistemu tipova
(S⊢ cond: bool, S⊢ e1: T1, S⊢ e2: T2, T is minimal upper bound of T1 and T2)
---------------------------------------------------------------------------------------------
S ⊢cond ? e1 : e2 : T
Sistem tipova je zadovoljen ako za svaku funkciju f sa telom B koji je u dosegu S možemo da
dokažemo da važi: S ⊢ WF(B)
Sad ovde treba navesti one formalne definicije pravila za petlje, break naredbu, if naredbu, while
naredbu itd... To sve treba pogledati na 04_semanticka_analiza_tipovi.txt nas tranici 28.
Propagiranje greške:
• Statička greška tipova se dešava kada ne možemo da dokažemo da izraz ima odgovarajući
tip
• Greške u tipovima se lako propagiraju. Na primer, ako ne možemo da dokažemo tip od e1
onda ne možemo da dokažemo ni tip od e1 + e2 i tako dalje
Kako to prevazići?
Tip Error:
• U sistem tipova uvodimo novi tip koji predstavlja grešku
• Ovaj tip je manji do svih drugih tipova i označava se sa ꓕ.
• Nekada se zove i bottom tip.
• Po definiciji važi ꓕ < A, za svako A.
• Kada otkriješ tip error pretvaramo se da smo dokazali tip ꓕ.
• Potrebno je unaprediti pravila zaključivanja tako da sadrže ꓕ.
Sada pravila izgledaju ovako (na primeru zbira, pokrili smo greške)
S⊢e1:T1
S⊢e2:T2
T 1 ≤ double
T 2 ≤ double
---------------
S ⊢ e 1 + e 2 : double
Oporavljanje od grešaka:
• Neophodno je da postoji neka vrsta oporavka od greške u semantičkom analizatoru
• Jedan tip oporavka je tip error koji smo uveli, ali postoje i drugi tipovi i slučajevi koje treba
rešiti kao što su poziv nepostojeće funkcije, deklaracija neispravnog tipa i slično.
Ne postoje ispravna i pogrešna rešenja, već samo lošiji ili bolji izbori.
26. Preopterećivanje funkcija.
Primer.
int Function();
int Function(int x);
int Function(double x);
int Function(Base b);
int Function(Derived d);
Jednostavno preopterećivanje:
• Počinjemo od skupa preopterećenih funkcija
• Najpre isfiltriramo funkcije koje se ne poklapaju i tako dobijamo skup kandidata.
• Ako je skup prazan prijavimo grešku
• Ako skup sadrži samo jednu funkciju, biramo nju
• Ako sadrži više biramo najbolju.
Preopterećivanje u realnosti:
• Preopterećivanje je obično značajno kompleksnije
• Na primer neki jezici (npr C, C++, Java) dozvoljavaju funkcije sa promenljivim brojem
argumenata. (variadic functions)
f is an identifier.
S⊢e0:M
f is a member function in class M.
f has type (T 1 , ..., T n ) → U
S ⊢ e i : R i for 1 ≤ i ≤ n
R i ≤ T i for 1 ≤ i ≤ n
-----------------------------
S ⊢ e 0 .f(e 1 , ..., e n ) : U
Kompletnost: Ako se program može ispravno izvršiti -onda važi da sistem tipova kaže da je on
ispravan u smislu tipova.
Saglasnost: Ako sistem tipova kaže da je ispravan - onda važi da se on može ispravno izvršiti.
Neka funkcija A predefiniše funkciju B, ali neka se ove funkcije razlikuju po povratnom tipu:
funkcije A i B su kovarijante po povratnom tipu ukoliko se tipovi argumenata poklapaju, a povratni
tip funkcije A se može konvertovati u povratni tip funkcije B.
f is an identifier.
S⊢e0:M
f is a member function in class M.
f has type (T 1 , ..., T n ) → U
S ⊢ e i : R i for 1 ≤ i ≤ n
R i ≤ T i for 1 ≤ i ≤ n
----------------------------
S ⊢ e 0 .f(e 1 , ..., e n ) : U
Kovarijante povratnog tipa su bezbedne, tj. pravilo koje definiše tipove je i dalje saglasno jer je sa
kovarijantom povratnog tipa obezbeđeno važenje uslova DynamicT ype(E) ≤ StaticT ype(E).
Mnogi programski jezici omogućuju kovarijante povratnog tipa. na primer C++ i Java
Neka funkcija A predefiniše funkciju B, ali neka se ove funkcije razlikuju po bar jednom
argumentu. Funkcija A je kovarijanta po argumentu funkcije B ukoliko se argumenti funkcija
poklapaju ili se neki argumenti funkcije A mogu pretvoriti u odgovarajuće argumente funkcije B.
f is an identifier.
S⊢e0:M
f is a member function in class M.
f has type (T 1 , ..., T n ) → U
S ⊢ e i : R i for 1 ≤ i ≤ n
R i ≤ T i for 1 ≤ i ≤ n
----------------------------------
S ⊢ e 0 .f(e 1 , ..., e n ) : U
void nothingFancy(Borken b) {
Print(b.missingFn());
}
}
int main() {
Fine f = new Borken;
f.nothingFancy(new Fine);
}
(iii) Kontravarijanta po argumentu funkcija
28. Izvršno okruženje i podaci. Enkodiranje osnovnih tipova, nizova i višedimenzionih nizova.
Prikaz podataka:
• Mašine obično podržavaju samo ograničen skup tipova
◦ Celobrojne vrednosti fiksirane dužine
◦ Brojeve u pokretnom zarezu
Nizovi:
• C style. Elementi su jedan za drugim u memoriji
• Java style. Elementi su jedan za drugim, stim što je prvi element int broj elemenata niza
• D style. Elementi su jedan za drugim, stoga što svaki element čuva pokazivač na prvi i član
posle poslednjeg člana.
Višedimenzioni:
• Predstavljaju se kao nizovi nizova
• Oblik zavisi od nizova koji se koriste.
Funkcije
Potrebno je odgovoriti na veliki broj pitanja:
• Kako izgleda dinamičko izvršavanje funkcija?
• Gde se izvršivi kod za funkcije nalazi?
• Kako se prosleđuju parametri funkcije?
• Gde se čuvaju lokalne promenljive?
29. Izvršno okruženje i funkcije. Aktivaciono stablo. Zatvorenja i korutine. Stek izvršavanja.
Aktivaciono stablo
• Aktivaciono stablo je stablo struktura koje predstavljaju sve pozive funkcija prilikom nekog
konkretnog izvršavanja programa.
• Aktivaciono stablo se ne može uvek odlučiti u fazi kompilacije.
• Statički ekvivalent je graf poziva funkcija
• Svaki aktivacioni slog čuva kontrolni link prema aktivacionom slogu koji ga je inicirao.
• Aktivaciono stablo je špageti stek.
Zatvorenja
*Jednom kad se funkcija vrati, njen aktivacioni slog ne može da bude ponovo referenciran.
Primer zatvorenja
function CreateCounter() {
var counter = 0;
return function() {
counter ++;
return counter;
}
}
Kontrolni link funkcije je pokazivač na funkciju koja ju je pozvala, koristi se da se odredi gde se
izvršavanje nastavlja kada funkcija završi sa radom.
Pristupni link funkcije je pokazivač prema aktivacionom slogu u kojem je funkcija kreirana.
Koriste ga ugnježdene funkcije da odrede lokaciju promenljivih kojima mogu da pristupe.
Korutine
*Svaki aktivacioni slog je ili završio izvršavanje ili je predak tekućeg aktivacionog sloga.
Podsetimo se:
• Podrutine su funkcije koje, kada se pozovu, izvršavaju se do završetka svog posla i vraćaju
kontrolu funkciji pozivaocu.
• Korutine su funkcije koje kada se pozovu, urade deo posla, vrate kontrolu pozivaocu, ali
kasnije mogu da nastave od mesta gde su stale.
Implementacija objekata je veoma složena. Veoma je teško napraviti izražajan i efikasan objektno
orijentisan jezik.
Strukture:
Struktura je tip koja sadrži kolekciju imenovanih vrednosti.
Najčešći pristup, postaviti svako polje redom u memoriji onim redom kojim je deklarisano.
Primer:
MyStruct* ms = new MyStruct;
ms->x = 137; store 137 0 bytes after ms
ms->y = 'A'; store 'A' 4 bytes after ms
ms->z = 2.71 store 2.71 8 bytes after ms
Jednostruko nasleđivanje
• Pojednostavljeno zbog memorije za izvedenu klasu D je zadat kao izgled memorije za baznu
klasu B za kojom sledi izgled memorije za preostale članove klase D
• Pokazivač na D i dalje vidi objekat B na početku memorije
31. Izvršno okruženje i funkcije članice klasa. Pokazivač this i dinamičko odredivanje poziva.
Funkcije članice klasa su kao i obične funkcije samo sadrže dve komplikacije:
• Kako znati za koji objekat je vezana funkcija?
• Kako znati koju funkciju pozvati u fazi izvršavanja?
U okviru funkcije klase ime this se koristi za tekući objekat. Ova informacija treba da se
iskomunicira funkciji.
Ideja: Tretiraj this kao implicitni parametar funkcije. Svaka funkcija koja ima n-argumenata ce u
stvarnosti imati n+1 argument zbog ovoga.
Kad se generiše kod, za poziv funkcije članice prosleđuje se još jedan parametar this koji
predstavlja odgovarajuči objekat.
U okviru funkcije članice this je samo još jedan parametar funkcije.
Dinamičko određivanje poziva znači odabir poziva funkcije u fazi izvršavanja u zavisnosti od
tekućeg dinamičkog tipa objekta.
Ideja: U fazi kompilacije napravi listu svih klasa i generiši naredni kod:
Tabela virtuelnih funkcija vtable je niz pokazivača na implementacije funkcija članica neke klase.
Da bi se pozvala funkcija članica klase:
• Odredi statički indeks u virtuelnoj tabeli
• Prati pokazivač koji se nalazi na tom indeksu u okviru vtable objekta da bi došao do koda
funkcije
• Pozovi tu funkciju
Zahtevi:
• Za prethodno rešenje napravljene su implicitne pretpostavke o jeziku koje dozvoljavaju
vtable radi korektno:
◦ Svi metodi su poznati statički: Možemo u fazi kompilacije da utvrdimo koji metod je
planiran na mestu poziva
◦ Jednostruko nasleđivanje: ne moramo da brinemo o građenju jedinstvene tabele vtable
prilikom višestrukog nasleđivanja
Višestruko nasleđivanje i interfejsi komlikuju izlged virtuelne tabele jer zahtevaju da metodi imaju
konzistentne pozicije kroz sve virtuelne tabele: može da se desi da virtuelna tabela ima
neiskorišćene unose (prazna mesta)
Zbog toga, prilikom višestrukog nasleđivanja i interfejsa obično se ne koriste virtuelne tabele.
Postoje različiti načini da se ovo implementira, jedan način je hibridni pristup: korišćenje virtuelnih
tabela za standradno nasleđivanje, a za interfejse koristi metod za poređenje metod zasnovan na
poređenju stringova.
33. Implementiranje dinamičkih provera tipova.
Mnogi jezici imaju potrebu za dinamičkim proverama tipova, Java instanceof, C++ dynamic_cast ,
jezici koji su dinamički tipizirani.
Ono što možemo da želimo da odredimo je da li je dinamički tip pretvoriv u neki drugi tip (<=)
Kako to implementirati?
Rešenje:
• Implementirati da svaka vtable čuva i pokazivač na svoju osnovnu klasu
• Provera da li se objekat može pretvoriti u neki tip S u fazi izvršavanja se svodi na praćenje
pokazivača na roditelja u okviru virtuelne tabele sve dok ne naiđe na tip S ili dok ne dođe do
tipa koji nema roditelja.
• Složenost je O(d) gde je d dubina hijerarhije.
Ideja je zasnovana na činjenici: objekat koji je statički tipa A je u fazi izvršavanja tipa B samo ako
je A pretvorivo u B, tj. A <= B.
• Ideja: Dodeli svakoj klasi jedinstveni prost broj. (kroz različite hijerarhije se mogu koristiti
različiti prosti brojevi)
• Postavi ključ klase da bude proizvod njenog prostog broja i svih prostih brojeva njenih
nadklasa
• Da bi se proverilo u fazi izvršavanja da li se objekat može pretvoriti u tip T:
◦ Pogledaj ključ objekta
◦ Ako je ključ T deljiv sa ključem objekta, onda se objekat može kovertovati
◦ Ako nije deljiv onda ne može.
• Metod se može koristiti i kod višestrukog nasleđivanja
Bulovske vrednosti se predstavljaju kao celobrojne vrednosti koje mogu imati vrednost nula ili
vrednost različitu od nule. Treba malo ovde pricati o if, while i for i kako se rešavaju preko goto.
Primer koda:
void main() {
SimpleFunction(137);
}
Troadresna verzija:
main:
BeginFunc 4;
_t0 = 137;
PushParam _t0;
LCall _SimpleFn;
PopParams 4;
endFunc;
Global pointer: MIPS takođe ima registar koji se naziva global pointer i koji čuva adresu globalnog
prostora.
Memorija na koju pokazuje globalni pointer se tretira kao niz vrednosti koji rastu naviše
Potrebno je odrediti za svaku globalnu promenljivu pomeraj u ovom nizu tj gde se ona tačno nalazi:
Global variable N --> adresa: global pointer + 4 * N
Rezime:
• Većina detalja je apstrakovana IR formatom
• Parametri počinju na adresi fp + 4 i rastu naviše
• Lokalne adrese počinju na adresi fp - 8 i rastu naniže
• Globalne promenljive počinju od adrese gp + 0 i rastu naviše
Pristup memoriji:
• Potrebno je da proširimo troadresni kod tako da ima mogućnost pristupa memoriji, tj.
potrebno je da pristup poljima objekta pretvorimo u relativne memorijske adrese
Da bi se generisao TAC (Troadresni kod) potrebno je još jednom obići rekurzivno AST:
• Generiši TAC izraz za svaki podizraz i podnaredbu
• Koristeći generisani TAC za podizraze/naredbe, generiši TAC za kompletne izraze i naredbe
Generisanje naredbi:
• Možemo proširiti funkciju codegen tako da radi sa naredbama
• Za razliku od codegen za izraze, codegen za naredbe ne vraća ime promenljive koja čuva
vrednost
38. Optimizacije medukoda. Graf kontrole toka.
Šta optimizujemo:
• Vreme izvršavanja
• Upotreba memorije
• Upotreba energije (biramo jednostavne instrukcije)
Osnovni blok:
• Niz IR instrukcija gde:
◦ Postoji jedna tačka ulaska u blok i nalazi se na vrhu bloka.
◦ Postoji jedna tačka izlaska iz bloka i nalazi se na kraju bloka.
v1 = a op b
v2 = a op b
tako da u vrednosti promenljivih a i b ne menjaju između između ove dve dodele, onda možemo da
prepišemo kod na sledeći način.
v1 = a op b
v2 = v1
v1 = v2
onda sve dok v1 i v2 nemaju ponovo dodeljene neke nove vrednosti možemo da zapišemo izraze
oblika
a = ... v1 ...
kao izraze oblike
a = ... v2 ...
Ova optimizacija stvara prostor za naredne optimizacije koje ćemo uskoro videti.
• Dodela promenljivoj v se naziva mrtva dodela ako se vrednost v nigde kasnije ne koristi.
• Eliminacija mrtvog koda uklanja mrtve dodele iz međureprezentacije
• Utvrđivanje da li je dodela mrtva zavisi od toga kojim promenljivama se dodeljuju vrednosti
i gde se ta dodela vrši.
•
Različite optimizacije koje smo do sada videli sve zapravov vode računa o samo malom delu
optimizacije.
• Optimizacija zajedničkih podizraza eliminiše nepotrebne naredbe.
• Prenos kopiranja pomaže u pronalaženju mrtvog koda
• Eliminacija mrtvog koda uklanja naredbe koje više nisu potrebne
• Da bi se izvukao maksimum ove operacije možda moraju da se izvrše više puta
40. Implementacija lokalnih optimizacija. Analiza dostupnih izraza. Analiza živosti.
Lokalna analiza osnovnog bloka se definiše kao uređena četvorka (D, V, F, I) gde je:
• D direction, odnosno smer. Može biti unapred ili unazad.
• V je skup vrednosti koje program ima u svakoj tački
• F je familija funkcija prenosa, koje definišu značenje svakog izraza kao funkciju f: V -> V
• I initial. To je početna informacija na vrhu ili dnu osnovnonog bloka
Analiza živosti:
• Smer: Unazad
• Domen: Skup promenljivih
• Funkcije prenosa: Za dati skup promenljivih i naredbu a = b + c:
◦ Ukloni a iz V (svaka prethodna vrednost od a je sada mrtva)
◦ Dodaj u V promenljive b i c (svaka prethodna vrednost od b i c je sada živa)
• Početne vrednosti: Zavisi od semantike jezika
Glavni izazovi:
• U okviru globalne analize, svaka naredba može imati više prethodnika.
• Globalna analiza mora imati neku vrstu kombinovanja informacija od svih prethodnika
osnovnog bloka
• U okviru globalne analize, može postojati puno puteva kroz kontrolni graf. (CFG)
• Može da bude potrebno da se ponovo izračunaju vrednosti više puta, tj. uvek kada nove
informacije postanu dostupne.
• Treba biti oprezan kako se ne bi desilo da se upadne u beskonačnu petlju.
• U okviru globalne analize sa petljama, svaki blok može da zavisi od svakog drugog bloka
◦ Da bismo ovo rešili, moramo da dodamo nekakve početne vrednosti svim blokovima.
Sumiranje razlika:
• Potrebno je da možemo da obradimo više prethodnika/sledbenika osnovnog bloka
• Potrebno je da možemo da obradimo više putanja kroz CFG i može biti potrebe da se iterira
više puta da bi se sračunala konačna vrednost.
• Potrebno je da možemo da dodelimo svakom osnovnom bloku smislenu osnovnu početnu
vrednost pre nego što ga analiziramo.
43. Globalna analiza živosti.
Formalna definicija:
• Polumreža sa operatorom spajanja je uređeni par (D, ∧) gde je
◦ D je skup koji označava domen elemenata
◦ ∧ je operator spajanja koji je
▪ idempotentan x ∧ x = x
▪ komutativan x ∧ y = y ∧ x
▪ asocijativan (x ∧ y) ∧ z = x ∧ (y ∧ z)
◦ Ako važi i x ∧ y = z , kažemo da je z spajanje za x i y
• Svaka polumreža sa operatorom spajanja ima element na vrhu koji se označava sa T takav
da je T ∧ x = x za svako x.
Opšti okvir
Ova vrsta analize naziva se okvir toka podataka. Uz dodatne pretpostavke može se koristiti da se
dokaže svojstvo završavanja analize.
U ovom trenutku imamo optimizovan IR kod koji treba da se konvertuje u odgovarajući jezik ciljne
mašine.
Ciljevi:
• Izabrati odgovarajuće mašinske instrukcije za svaku IR instrukciju
• Podeliti resurse mađine (registri, keš)
• Implementirati detalje niskog nivoa izvršnog okruženja
Memorija:
• Postoji velika razlika u brzini i veličini memorije.
◦ RAM je brza ali skupa, hard disk je jeftin, ali veoma spor.
◦ Osnovna ideja je dobiti najbolje iz svih svetova korišćenjem različitih vrsta memorije
Izazovi generisanja koda:
• Skoro svi jezici imaju veoma grub pogled na memorijsku hijerarhiju: sve promenljive žive u
"memoriji" bez zalaženja u detalje na koju se tačno memoriju misli. Jedino se sa diskom i
mrežom eksplicitno rukuje drugačije.
• Izazov generisanja koda je da se objekti postave na takav način koji maksimizuje prednosti
memorijske hijerarhije.
◦ Dodatno, to je potrebno uraditi potpuno automatski, tj bez pomoći programera.
Mane:
• Ogroman broj učitavanja
• Jako je neefikasan
• Veoma lako postaje za red veličine ili dva reda veličine sporiji kod nego što je potrebno
• Potpuno neprihvatljiv pristup za kompajlere
Prednosti:
• Jednostavnost
• Može da prevede svaki deo IR koda direktno u asembler u jednom prolazu
• Nikada ne mora da brine da će ostati bez dovoljno registara
• Ne mora da brine ni o pozivima funkcija ili o registrima specijalne namene
• Dobar je samo ako nam je potreban prototip kompajlera koji radi
Ta ideja bi trebalo da umanji broj ćitanja/pisanja u memoriju i uopšte ukupno korišćenje memorije
Da bi se to ostvarilo, potrebno je da se odgovori na naredna dva pitanja:
• U koje registre stavljamo promenjive?
• Šta se dešava kada nemamo dovoljno registara?
Konzistentnost registara:
• U svakoj tački programa svaka varijabla mora da bude na istoj lokaciji
◦ Ovo ne znači da svaka varijabla mora da bude na istoj lokaciji sve vreme, ali bez obzira
kojom putanjom smo došči očekujemo da nam je npr varijabla d u poslenjem bloku
prethodne slike u istom registru.
• U svakoj tački programa, svaki registar sadrži najviše jednu živu promenljivu
◦ Ovo znači da možemo da podelimo više varijabli istom registru ako nikada dve od njih
neće biti čitane zajedno
Živi opsezi i intervali
• Promenljiva je živa u nekoj tački u programu ako se njena vrednost čita pre nego što se u nju
ponovo nešto upiše
• Živost promenljivih se može pronaći korišćenjem globalne analize živosti
• Živi opseg promenljive je skup tačaka u programu u kojim je promenljiva živa
• Živ interval promenljive je najmanji interval IR koda koji sadrži sve žive opsege
promenljive
◦ Živ interval je manje precizan od živih opsega ali je sa njim lakše raditi.
• Ako su dati živi intervali promenljivih u programu, možemo alocirati registre koristeći
jednostavan pohlepni algoritam
◦ Ideja: prati koji registri su slobodni u svakoj tački
◦ Kada živi interval počne, daj promenljivoj slobodan registar
◦ Kada se živi interval završi, oslobodi odgovarajući registar
◦ Problem: šta ako nemamo dovoljno registara?
Register spilling
Ako za promenljivu v ne postoji slobodan registar, onda je potrebno njenu vrednost prosuti.
Kada je promenljiva prosuta, ona se čuva u memoriji umesto u registru
Kada nam treba registar za promenljivu koja je bila prosutra potrebno je:
• da iselimo neki postojeći registar u memoriju
• da u taj registar učitamo vrednost prosute promenljive
• kada završimo, potrebno je da sadržaj registra vratimo u memoriju i da u taj registar vratimo
originalnu vrednost koja je tu bila.
Analiza algoritma:
• Prednosti:
◦ veoma je efikasan, nakon računanja intervala živosti, izvršava se u linearnom
vremenu
◦ Daje dosta kvalitetan kod
◦ Alokacija radi u jednom prolazu
◦ Često se koristi za JIT kompajlere
• Mane:
◦ Neprecizan jer koristi intervale a ne doseg
◦ Postoje bolje tehnike
Čajtinov algoritam
Intuicija:
• Pretpostavimo da pokušavamo da obojimo sa k boja graf. Pronađi čvor sa manje od k ivica
• Ako obrišemo ovaj čvor iz grafa i obojimo ono što preostane, možemo da nađemo bojenje i
za ovaj čvor kada ga vratimo nazad
• Razlog: za manje od k suseda, neka boja je sigurno neiskorišćena
Algoritam:
• Pronađi čvor sa manje od k izlaznih ivica
• Skloni ga iz grafa
• Rekurzivno oboj ostatak grafa
• Vrati čvor nazad
• Dodeli mu validnu boju
Unapređenja algoritma:
• Izaberi promenljivu koja će biti prosuta koristeći neku pametnu heuristiku (najmanje
korišćena recimo)
• Nakon prosipanja promenljive izračunati opet rig zasnovan na tom prosipanju
49. Rasporedivanje instrukcija. Graf zavisnosti podataka.
Zavisnost među podacima u mašinskom kodu je skup instrukcija čije ponašanje zavisi jedno od
druge.
Intuitivno, skup instrukcija koje ne mogu da se poređaju na drugaliji način.
Postoje tri vrste zavisnosti: čitanje nakon pisanja, pisanje nakon čitanja, pisanje nakon pisanja
Graf koji prikazuje zavisnosti podataka u okviru osnovnog bloka naziva se graf zavisnosti podataka
To je direktan aciklični graf. Direktan, jer uvek jedna instrukcija zavisi od druge. Acikličan, jer nisu
moguće aciklične zavisnosti.
Mogu se rasporediti instrukcije u okviru osnovnog bloka u bio kom redosledu sve dok se ne
raspodele instrukcije tako da neka instrukcija prethodi svom roditelju.
Ideja: napravi topološko sortiranje zavisnosti podataka i poređaj instrukcije u tom redosledu.
Problem:
• Može postojati puno ispravnih topoloških udeđenja grafa zavisnosti podataka
◦ Kako izabrati onaj koji je dobar?
▪ U opštem slučaju, pronalaženje najboljeg rasporeda instrukcija je NP-težak problem
▪ U praksi se koriste heuristike
Raspoređivanje instrukcija
Većina keš memorija je dizajnirano da iskoristi ove lokalnosti tako što se u kešu da drže skoro
adresirani objekti i tako što se u keš ubacuje i sadržaj memorije u blizini.
Poboljšanje lokalnosti
• Programeri obično pišu kod bez razumevanja o posledicama lokalnosti jer jezici ne
prikazuju detalje memorije.
• Neki kompajleri su sposobni da prepišu kod tako da se lokalnost iskoristi.
• Primer takve optimizacije je preraspoređivanje petlji.
Optimizacije koda
• Postoje i razne druge optimizacije koje se mogu sprovesti na nivou izgenerisanog koda