Errata. Seznam chyb v textu bakalářské práce. Překladač s optimalizací výsledného kódu, 2013, Petr Krajník. Stav objevených chyb z 9.



Podobné dokumenty
8) Jaké jsou důvody pro použití víceprůchodového překladače Dříve hlavně kvůli úspoře paměti, dnes spíše z důvodu optimalizace

Syntaxí řízený překlad

GENEROVÁNÍ KÓDU 9. SHRNUTÍ - PŘÍKLAD POSTUPU PŘEKLADU VSTUPNÍHO PROGRAMU (ZA POUŽITÍ DOSUD ZNÁMÝCH TECHNIK)

PROGRAMOVACÍ JAZYKY A PŘEKLADAČE FORMALISMY PRO SYNTAXÍ ŘÍZENÝ PŘEKLAD: PŘEKLADOVÉ A ATRIBUTOVÉ GRAMATIKY.

GRAFY A GRAFOVÉ ALGORITMY

Interpret jazyka IFJ2011

PROGRAMOVACÍ JAZYKY A PŘEKLADAČE STRUKTURA PŘEKLADAČE

1 Úvod do kompilátorů

přirozený algoritmus seřadí prvky 1,3,2,8,9,7 a prvky 4,5,6 nechává Metody řazení se dělí:

5 Rekurze a zásobník. Rekurzivní volání metody

Operační systémy Linux, Mac OS X a jejich srovnání

Algoritmizace I. Ak. rok 2015/2016 vbp 1. ze 132

VÝUKOVÝ MATERIÁL. Bratislavská 2166, Varnsdorf, IČO: tel Číslo projektu

Binární vyhledávací stromy II

MATURITNÍ OTÁZKY ELEKTROTECHNIKA - POČÍTAČOVÉ SYSTÉMY 2003/2004 PROGRAMOVÉ VYBAVENÍ POČÍTAČŮ

Automaty a gramatiky(bi-aag) Motivace. 1. Základní pojmy. 2 domácí úkoly po 6 bodech 3 testy za bodů celkem 40 bodů

Metodika. Architecture First. Rudolf Pecinovský

Pokud nebude na příkazové řádce uveden právě jeden argument, vypište chybové hlášení a stručný

13. Třídící algoritmy a násobení matic

Kolekce ArrayList. Deklarace proměnných. Import. Vytvoření prázdné kolekce. napsal Pajclín

Implementace numerických metod v jazyce C a Python

OSTRAVSKÁ UNIVERZITA V OSTRAVĚ

Kolaborativní aplikace

REGISTRY VE VEŘEJNÉ SPRÁVĚ

12. Aproximační algoritmy

Automaty a gramatiky(bi-aag) Formální překlady. 5. Překladové konečné automaty. h(ε) = ε, h(xa) = h(x)h(a), x, x T, a T.

Řízení pohybu stanice v simulačním prostředí OPNET Modeler podle mapového podkladu

Abstrakt. Klíčová slova. Abstract. Key words

NPRG030 Programování I 3/2 Z --- NPRG031 Programování II --- 2/2 Z, Zk

Intervalové stromy. Představme si, že máme posloupnost celých čísel p 0, p 1,... p N 1, se kterou budeme. 1. Změna jednoho čísla v posloupnosti.

Úvod z historie. Kompilátory. Kompilace / Kompilátor Compile / Compiler. Pojem kompilátoru. Úvod z historie

8. Geometrie vrací úder (sepsal Pavel Klavík)

Kapitola 11. Vzdálenost v grafech Matice sousednosti a počty sledů

IB111 Úvod do programování skrze Python Přednáška 13

ZÁKLADY PROGRAMOVÁNÍ. Mgr. Vladislav BEDNÁŘ , 5.1 a 5.2 8/14

PROGRAMOVACÍ JAZYKY A PŘEKLADAČE PŘEKLADY TYPICKÝCH JAZYKOVÝCH KONSTRUKCÍ PROGRAMOVACÍCH JAZYKŮ.

zswi/pc-testování.d 10. května

V 70. letech výzkumy četnosti výskytu instrukcí ukázaly, že programátoři a

11. Přehled prog. jazyků

zejména Dijkstrův algoritmus pro hledání minimální cesty a hladový algoritmus pro hledání minimální kostry.

Implementace LL(1) překladů

3 Vývojová prostředí, základní prvky jazyka Java, konvence jazyka Java

3 Co je algoritmus? Trocha historie Definice algoritmu Vlastnosti algoritmu... 3

PREPROCESOR POKRAČOVÁNÍ

Vzdálené řízení modelu připojeného k programovatelnému automatu

ZÁVAZNÉ POKYNY PRO VYPRACOVÁNÍ BAKALÁŘSKÉ, DIPLOMOVÉ A DISERTAČNÍ PRÁCE

Hardwarová realizace konečných automatů

Stromy, haldy, prioritní fronty

ČVUT FAKULTA ELEKTROTECHNICKÁ, TECHNICKÁ 2, PRAHA, ČESKÁ REPUBLIKA. Semestrální projekt. Systém speech2text (pracovní název)

VYSOKÁ ŠKOLA EKONOMICKÁ V PRAZE. Optimalizace trasy při revizích elektrospotřebičů

Infrastruktura UML. Modelování struktury v UML. Superstruktura UML. Notace objektů. Diagramy objektů

BAKALÁŘSKÁ PRÁCE. Numerické metody jednorozměrné minimalizace

Úvod do architektur personálních počítačů

3D Vizualizace muzea vojenské výzbroje

Implementace A* algoritmu na konkrétní problém orientace v prostoru budov

Projekty pro výuku programování v jazyce Java

Architektury CISC a RISC, uplatnění v personálních počítačích

Aplikovaná informatika

Uživatelem řízená navigace v univerzitním informačním systému

PRÉCIS STRUKTUROVANÁ DATABÁZE JAKO ODPOVĚĎ NA NESTRUKTUROVANÝ DOTAZ. Dominik Fišer, Jiří Schejbal

VYUŽITÍ KNIHOVNY SWING PROGRAMOVACÍHO JAZYKU JAVA PŘI TVORBĚ UŽIVATELSKÉHO ROZHRANÍ SYSTÉMU "HOST PC - TARGET PC" PRO ŘÍZENÍ POLOVODIČOVÝCH MĚNIČŮ

POLOHOVÁNÍ ULTRAZVUKOVÉHO SENZORU

Programování v Javě I. Leden 2008

Implementace seznamů do prostředí DELPHI pomocí lineárního seznamu

Stabilita v procesním průmyslu

Vizuální programovací jazyk

Distanční opora předmětu: Programování v jazyce C Tématický blok č. 8: Dynamické datové struktury, ladění programů Autor: RNDr. Jan Lánský, Ph.D.

Programování v Javě I. Únor 2009

ZÁKLADY PROGRAMOVÁNÍ. Mgr. Vladislav BEDNÁŘ /14

Cílem kapitoly je seznámit studenta se strukturou programu a jeho překladem.

Vyučovací hodina. 1vyučovací hodina: 2vyučovací hodiny: Opakování z minulé hodiny. Procvičení nové látky

Úvod do PHP s přihlédnutím k MySQL

IB108 Sada 1, Příklad 1 Vypracovali: Tomáš Krajča (255676), Martin Milata (256615)

Výpočetní složitost I

Výpočetní modely pro rozpoznávání bezkontextových jazyků zásobníkové automaty LL(k) a LR(k) analyzátory

Tabulka symbolů. Vazba (binding) Vazba - příklad. Deklarace a definice. Miroslav Beneš Dušan Kolář

Kubatova Y36SAP procesor - control unit obvodový a mikroprogramový řadič RISC Y36SAP-control unit 1

SYSTÉM PRO KONFIGURACI KOMUNIKAČNÍCH TERMINÁLŮ A VIZUALIZACI STAVOVÝCH DAT Z KOLEJOVÝCH VOZIDEL

STROMOVE ALGORITMY Prohledavani do sirky (level-order) Po vodorovnejch carach fronta

Programovací jazyky. imperativní (procedurální) neimperativní (neprocedurální) assembler (jazyk symbolických instrukcí)

Jarníkův algoritmus. Obsah. Popis

Algoritmy a datové struktury

Zpráva o průběhu přijímacího řízení na vysokých školách dle Vyhlášky MŠMT č. 343/2002 a její změně 276/2004 Sb.

Návrh a implementace algoritmů pro adaptivní řízení průmyslových robotů

Vysoká škola chemicko-technologická v Praze Fakulta chemicko-inženýrská Ústav fyziky a měřicí techniky

PROGRAMOVACÍ JAZYKY A PŘEKLADAČE LL SYNTAKTICKÁ ANALÝZA DOKONČENÍ, IMPLEMENTACE.

Dynamicky vázané metody. Pozdní vazba, virtuální metody

NEW TRANSPORT TECHNOLOGY - BUSES ON CALL

Práce s velkými sestavami

Teoretické minimum z PJV

Syntaxí řízený překlad

SPECIFICKÝCH MIKROPROGRAMOVÝCH ARCHITEKTUR

IMPLEMENTACE AUTOMATIZOVANÉHO MĚŘENÍ HRTF V MATLABU

1 Nejkratší cesta grafem

VÝUKOVÝ MATERIÁL. Bratislavská 2166, Varnsdorf, IČO: tel Číslo projektu

PROGRAMOVÁNÍ MIKROPOČÍTAČŮ CVIČENÍ 10

Aritmetika s velkými čísly na čipové kartě

Tento text je stručným shrnutím těch tvrzení Ramseyovy teorie, která zazněla

George J. Klir. State University of New York (SUNY) Binghamton, New York 13902, USA

Šifrování/Dešifrování s použitím hesla

Transkript:

Errata Seznam chyb v textu bakalářské práce Překladač s optimalizací výsledného kódu, 2013, Petr Krajník Stav objevených chyb z 9. ledna 2013 Zde jsou uvedeny všechny dosud objevených chyb v textu bakalářské práce. Většina z nich byla způsobena namotáním některých částí z předchozího zápisu bakalářské práce. Nyní následuje výčet chyb s popisem. 1. Chybějící citace (v textu jen [? ]) jsou: (a) Strana 28. Správně Sethi-Ullman (ne Levi-Sethi) algoritmus. AHO, Alfred V, Monica S. LAM, Ravi SETHI a Jeffrey D. ULLMAN. Compiler: Prinzipien, Techniken und Werkzeuge. 2., aktualisierte Aufl. München: Pearson Studium, 2008, 1253 s. ISBN 978-3-8273-7097-6. (b) Strana 23. Domovská strana gen. flex. Flex: The Fast Lexical Analyzer [online]. c2008 [cit. 2013-01-01]. Dostupné z: http://flex.sourceforge.net/. (c) Na staně 45. v příloze s gramatikou. NEŠVERA, Šimon. PROGRAMOVACÍ JAZYKY: Cvičení. Dotisk prvního vydání. Praha: Nakladatelství ČVUT, prosinec 2005, 114 s. ISBN 80-01-02522-5. 2. Příloha s gramatikou byla převzata z prvního zápisu. Uvedená gramatika obsahuje o něco více operátorů, než je implementováno. Jelikož jsem byl zahlcen různými operátory, tak jsem redukoval jejich počet. Zde je výčet pravidel, které by v gramatice být neměly. (a) Operátor čárka by měl být vynechán (pravidlo 42 44). (b) Logické operátory (pravidlo 48 53). (c) Některé prefixové operátory (pravidla 76 78). (d) Některé postfixové operátory (pravidla 81 82). Jinak gramatika v příloze plně odpovídá realitě. 3. V uživatelské manuálu, přiloženém jako příloha k práci, se přeložený překladač nazývá kcc. Jelikož jsem zapomněl výstupní soubor přejmenovat, tak se jmenuje Compiler. Toto je nutné při čtení příkladů v manuálu zohlednit. 4. Přepínače uživatelském manuálu neodpovídají realitě. Pro zjištění správných přepínačů stačí Compiler -h. Navíce již nejsou implementovány pomocí GNU getopt. Význam přepínačů je stejný, ale jejich zápis je jiný. 5. Adresář s uživatelským manuálem je prázdný. Měl vněm být umístěn stejný manuál, jako je přiložen k textu práce. 1

Errata Seznam chyb v projektu bakalářské práce Překladač s optimalizací výsledného kódu, 2013, Petr Krajník Stav objevených chyb z 9. ledna 2013 Zde jsou všechny dosud objevené chyby v překladači. 1. Chybné varování kódu 9 (Použití neinicializované proměnné). Při prvním přiřazení do proměnné je vydáno toto varování. Máme tedy k každé proměnné jedno varování. Způsobeno je to tím, že levá strana přiřazení je vyhodnocena (a vydáno varování), pak teprve se definuje daná proměnná. 2. Při nepoužití nějakého parametru funkce, může dojít k rozhození argumentů. Nepoužitým parametrům nepřiřadí adresa a přiřadí se dalšímu parametru nebo proměnné. Když jsem testoval, tak jsem vždy všechny zavedené parametry použil a neodhalil tento nedostatek. 3. U funkcí s více parametry dochází k obrácení pořadí těchto parametrů. Nečekal jsem totiž, že JVM sama obrátí pořadí parametrů a zadávám je v opačném pořadí. 4. Nejsou implementovány globální proměnné. Při jejich použití je hozena výjimka. 5. Přepínač -c nemá žádný efekt. Existence funkce main se nekontroluje. Chovaní je tedy stejné jako při zapnutém přepínači -c. 2

ČESKÉ VYSOKÉ UČENÍ TECHNICKÉ V PRAZE Fakulta elektrotechnická Katedra počítačů BAKALÁŘSKÁ PRÁCE Překladač s optimalizací výsledného kódu Petr Krajník krajnpet@fel.cvut.cz, petrkrajnik@centrum.cz Vedoucí práce: Ing. Ivan Šimeček, Ph.D Praha zima 2012/2013

Informace o bakalářské práci Titul práce Překladač s optimalizací výsledného kódu Rok tovrby Zimní semestr akademického roku 2012/2013 Počet stránek xvi + 56 Škola Autor Studijní program Studijní obor Vedoucí práce Oponent České vysoké učení technické v Praze Fakulta elektrotechnická Technická 2 166 27 Praha 6 Petr Krajník Elektrotechnika a informatika (bakalářský), strukturovaný Výpočetní technika Ing. Ivan Šimeček, Ph.D Mgr. Michal Píše Sazba textu byla provedena pomocí typografického systému L A TEX 2ε. Pro sazbu poděkování bylo použito standardní PostScriptové písmo Zapf Chancery. Všechny ostatní písma použité v této práci jsou z projektu Latin Modern. Písma projektu Latin Modern jsou volně dostupné na <http://www.gust.org.pl/projects/e-foundry/latin-modern>. Copyright c 2012 Petr Krajník

Prohlášení Prohlašuji, že jsem práci vypracoval samostatně a použil jsem pouze podklady uvedené v přiloženém seznamu. Nemám závažný důvod proti užití tohoto školního díla ve smyslu 60 Zákona č. 121/2000 Sb., o právu autorském, o právech souvisejících s právem autorským a o změně některých zákonů (autorský zákon). V Praze dne 4. ledna 2013

Poděkování Chtěl bych poděkovat rodičům za veškerou podporu při tvorbě práce. Dále bych chtěl také za vše poděkovat vedoucímu práce. Zejména za trpělivost. Poděkovat bych chtěl také všem, kteří mě podpořili při tvorbě této práce.

Abstrakt CZ Překladač s optimalizací výsledného kódu Tato práce se zabývá konstrukcí a implementací kompilačního překladače s optimalizací výstupního kódu. Konkrétně je implementován překladač z zjednodušeného jazyka C do jazyka symbolických instrukcí Java Virtual Machine, který můžeme pak dále zpracovat. Čtenář bude proveden konstrukcí tohoto překladače od návrhu architektury a definice vstupního jazyka, přes přední část, až po zadní část překladače. Popsány veškeré použité metody popř. jejich implementace, které byly při tvorbě použity. Probírána a implementována je také optimalizační část překladače. Těžiště je na systémově nezávislých optimalizacích pro zvýšení rychlosti výsledného kódu. Nejdříve jsou probírány různé metody optimalizací na rychlost, z nichž se zaměříme zejména na optimalizace abstraktního syntaktického stromu, interprocedurální optimalizace a optimalizace přímým vkládáním kódu. Z těchto tří skupin jsou implementovány jejich nejvýznamnější zástupci. Jedná se o spojování konstant, odstranění mrtvého kódu a přímé vkládání funkcí. Výsledkem je překladač s optimalizátorem, který prochází testováním, jehož výsledky jsou k závěru práce zhodnoceny. Klíčová slova: překladač, optimalizace, abstraktní syntaktický strom, JVM, generování kódu, syntaktická analýza, Jasmin, Flex Abstract EN Optimizing compiler This work describes construction and implementation of a compiler with optimization of the resulting output code. Concretely compiler is implemented as a translator of a simplified C language into Java Virtual Machine assembly language. The reader is guided thru construction of this compiler from architecture design and input language definition, through back-end, to the back-end. Every method resp. her implementation used while creation is described. Also discussed and implemented is the optimization part of the compiler. The focus is on system independent optimization methods for increasing speed of the resulting code. First are discussed different optimization methods for speed, from which we focus on optimization of abstract syntax tree, interprocedural optimization and optimization by code inlining. From these three types the main representative methods are implemented. Implemented methods are constant merging, dead code elimination and function inlining. The result is an optimizing compiler, which will be tested, and the results are evaluated at the end of this work. Key Words: compiler, optimization, abstract syntax tree, JVM, code generation, parsing, Jasmin, Flex ix

x

Obsah Obsah Seznam obrázků xi xv 1 Úvod 1 1.1 Typy překladačů.............................. 2 1.2 Shrnutí úvodu................................ 3 2 Teoretické základy 5 2.1 Grafy..................................... 5 2.1.1 Průchod grafu do hloubky..................... 5 2.1.2 Stomy................................ 5 2.2 Formální jazyky............................... 6 2.3 Gramatiky.................................. 6 2.3.1 Regulární gramatiky........................ 6 2.3.2 Překladové gramatiky....................... 6 2.3.3 Atributované gramatiky...................... 6 Atributy speciálních symbolů................... 7 2.4 Končené automaty............................. 7 2.4.1 Regulární výrazy a gramatiky................... 7 2.5 Zásobníkové automaty........................... 7 2.6 Syntaktická analýza............................. 7 2.6.1 LL(1) syntaktický analyzátor................... 8 Rozkladová tabulka a její návrh.................. 9 Implementace LL(1) analyzátoru................. 9 2.7 Definice programovacího jazyka...................... 9 3 Řešený problém, cíle, struktura práce 11 3.1 Stanovení cílů................................ 11 3.2 Struktura práce............................... 12 4 Analýza existujících implementací 13 4.1 NestedVM.................................. 13 4.2 LLVM.................................... 13 4.3 C2J..................................... 14 4.4 Java GCC Backend............................. 14 4.5 Jiná řešení.................................. 14 4.6 Shrnutí a zhodnocení............................ 15 5 Architektura překladače 17 xi

5.1 Části překladače.............................. 18 5.1.1 Preprocessing............................ 18 5.1.2 Přední čast překladače....................... 18 5.1.3 Zadní část překladače........................ 19 5.1.4 Postprocessing........................... 19 5.2 Architektura implementovaného překladače................ 20 5.2.1 Řízení překladu........................... 20 5.2.2 Tabulka symbolů.......................... 21 5.2.3 Průběh překladu.......................... 21 5.3 Chybové výstupy a logování........................ 22 5.3.1 Systémové chyby.......................... 22 6 Přední část překladače 23 6.1 Preprocessing................................ 23 6.2 Lexikální analýza.............................. 23 6.2.1 Generátory lexikálních analyzátorů................ 23 6.3 Vnitřní forma................................ 24 6.3.1 Abstraktní syntaktický strom................... 24 6.3.2 Průchod AST............................ 24 6.4 Syntaktická analýza............................. 25 6.4.1 Návrh gramatiky.......................... 25 6.4.2 Optimalizace gramatiky pro LL(1) analýzu............ 25 6.4.3 Zpracování chyb........................... 25 6.5 Sémantická analýza............................. 25 7 Zadní část překladače 27 7.1 Optimalizace................................ 27 7.2 Přidělování registrů............................. 27 7.3 Přidělování adres.............................. 28 7.4 Generování výstupního kódu........................ 28 8 Optimalizace 31 8.1 Klasifikace optimalizací........................... 31 8.1.1 Globální a lokální optimalizace.................. 31 8.1.2 Cíle optimalizací.......................... 32 8.1.3 Fáze vykonání optimalizací..................... 32 8.1.4 Systémově závislé a nezávislé................... 32 8.1.5 Grafy toku řízení programu.................... 32 8.2 Tradiční optimalizační metody....................... 32 8.3 Optimalizace na abstraktním syntaktickém stromu............ 32 8.3.1 Spojování konstant......................... 33 8.3.2 Přesun invariantu z cyklu..................... 33 8.3.3 Odstranění mrtvého kódu..................... 33 8.3.4 Hledání společných podstromů................... 34 8.3.5 Optimalizace matematickými zákony............... 34 8.4 Interprocedurální optimalizace....................... 34 Využití a konstrukce grafu volání................. 34 xii

8.4.1 Optimalizace použití konstant................... 35 8.4.2 Odstranění nevolaných funcí.................... 35 8.5 Optimalizace přímým vkládáním...................... 35 8.6 Úvod a rozdělení.............................. 35 9 Testování 37 Literatura 41 A Použité zkratky 43 A.1 Seznam zkratek............................... 43 B Bezkontextová gramatika překladače 45 B.1 Bezkontextová LL(1) gramatika...................... 45 C Uživatelský manuál překladače 49 C.1 Co všechno pořebujeme........................... 49 C.2 Instalace a deinstalace........................... 49 C.3 Přepínače.................................. 49 C.4 Vstupní soubory............................... 50 C.5 Výstupní soubory.............................. 50 C.6 Kompilace.................................. 50 C.7 Chybové a diagnostické zprávy....................... 50 C.8 Příklady použití............................... 51 C.8.1 Použití kódu jako knihovny.................... 51 D Obsah přiloženého CD 53 D.1 Volba jména CD.............................. 53 D.2 Struktura CD................................ 53 D.2.1 Adresář Text............................ 54 Formáty textu............................ 55 D.2.2 Adresář Projekt........................... 55 D.3 Další informace............................... 56 xiii

xiv

Seznam obrázků 5.1 Obecná architektura překladače....................... 17 5.2 Architektura implementovaného překladače................ 20 6.1 Diagram dědičnosti příkazů......................... 24 6.2 Diagram dědičnosti výrazů......................... 24 7.1 Schéma zadní části překladače....................... 28 xv

xvi

Kapitola 1 Úvod Spustitelný program pro určitý počítač se skládá ze zakódovaných instrukcí vykonatelných daným procesorem počítače. Instrukce jsou uloženy, ve většině případů, ve formátu s kterým umí pracovat operační systém. Používaný formát skoro vždy obsahuje mnoho dalších informací, které umožňují efektivní zavedení a spuštění programu operačním systémem. Existuje jen málo formátů, které obsahují jen zakódované instrukce (podle architektury počítače případně také rezervu pro datovou paměť). Příkladem jednoduchého formátu je starší spustitelný formát COM, který obsahuje jen binárně zakódovaný strojový kód. Nicméně pokud chceme psát programy, tak musíme znát veškeré detaily daného systému, pro který program píšeme. Při psaní programů můžeme postupovat několika způsoby. Programy lze psát přímo ve strojovém kódu používaného procesoru (většinou pomocí hexa editoru), pokud jsou programy a formáty spustitelných souborů dosti jednoduché. Zdrojový kód je kódem strojovým. Mimo to, že víme jak vypadá výsledný strojový kód, má tato metoda snad všechny nevýhody, s kterými se jako programátor můžeme setkat. Mezi nevýhody patří špatná čitelnost, veliká náchylnost k zavedení chyb programátorem, špatně se provádějí změny a psaní i jednoduchých programů se tím stává velice náročné. Navíc je potřeba absolutní znalost systému, procesoru, jeho instrukční sady a dalších věcí, bez kterých nebudeme schopni vůbec nějaký program napsat. Výsledný kód bude ještě k tomu vázán na určitou architekturu a nebude přenositelný. Nevýhod je samozřejmě ještě víc. Další, lepší, metodou je použití jazyka symbolických instrukcí pro zápis programu. Zakódování instrukcí a vytvoření spustitelného souboru převezme, k tomu účelu vytvořený, překladač nazývaný assembler. Assemblery mimo překlad můžou ještě před generováním výstupu jemně doladit program pro získání lepšího výsledku nebo nám dát k dispozici důležité diagnostické informace. Nejsou nijak složité a mají poměrné nízké paměťové nároky. Díky tomuto faktu byly realizovatelné pro automatizaci překladu již v počátcích existence počítačů. Odstraněn byl tím problém čitelnosti a nutnosti znát výstupní formáty spustitelných souborů, protože o to se postará assembler. K tomu se musí dodat, že čitelnost stále není nijak úchvatná a nadále zůstává požadována znalost procesoru a jeho instrukční sady. Jelikož existuje hodně procesorů s různými instrukčními sadami a architekturami, tak náš kód bude použitelný jen na určité platformě nebo na nějaké s ní kompatibilní (např. x86). Faktem je, že jedině tímto způsobem jsme schopni využít maximálně prostředků daného počítače. Neztrácíme kontrolu nad tím, co bude počítač dělat. Z tohoto důvodu je psaní programů v assembleru jasnou volbou, pokud požadujeme rychlý kód do detailu odladěný. Většina překladačů překládá do jazyka symbolických 1

Kapitola 1. Úvod instrukcí. Díky tomu bude vždy potřeba existence assemblerů, protože pro nás zaručují spojení s procesorem cílového počítače. Pokud chceme psát přenositelný a čitelný kód, tak je potřeba pro zápis zdrojových kódů použít nějaký vyšší programovací jazyk. Tyto jazyky zavádějí obecné konstrukce (cykly, podprogramy, atd.), na jejichž základě lze pak vygenerovat kód pro určitou platformu. Vyšší programovací jazyky nejsou žádnou novinkou. Již v počátcích počítačů se psaly složitější programy v jazycích vyšších. Zde je nutné dodat, že někdejší vyšší programovací jazyky neměly mnoho společného s těmi co používáme dnes. Zdroje v těchto jazycích byly pak přeloženy do jazyka symbolických instrukcí nebo přímo do strojového kódu. Kódy byly tenkrát překládány ručně. Výsledkem snahy tento proces automatizovat, vznikla disciplína konstrukce překladačů jako podmnožina teoretické informatiky, zabývající tvorbou překladačů a algoritmů s nimi spojenými. Hlavní problémem prvních překladačů (padesátá léta 20. stol.) byl nedostatek paměti, způsobený vyššími paměťovými nároky při zpracováním vyšších programovacích jazyků. Překladače z vyšších jazyků do jazyka symbolických instrukcí nazýváme kompilátory. S nárůstem výkonnosti počítačů a jejich paměti došlo k umožnění vývoje překladačů, ale z důvodů narůstající složitosti vyšších programovacích jazyků přišly jiné problémy s jejich zpracováním. Veliký zájem o překladače přinesl poměrně rychlý nárůst metod a algoritmů, které se i dnes stále při tvorbě překladače používají. V dnešní době tím existuje velké množství léta ověřených algoritmů, které nám umožňují systematicky implementovat překladače. Asi největším problémem u vyšších programovací jazyků je, že schvalují programátorovi zavádění neefektivních operací. To nemusí být dáno neschopností programátora, ale i jazykem samotným, který se vzhledem ke své univerzálnosti musí vyhýbat konkrétnostem cílového jazyka překladače. Výsledkem je neefektivní kód. Odezvou na tento problém byl vznik automatických optimalizátorů. Veliký rozvoj proběhl v sedmdesátých letech 20. století, protože za tuto dobu narostla paměť a výpočetní výkon tak, že umožnil jejich implementaci a vývoj. Optimalizátor je součást překladače snažící se vytvořit co nejkvalitnější kód podle programátorových kritérií. Mezi nejčastější kritéria patří optimalizace na rychlost, nebo na paměťovou efektivitu. Samozřejmě mohou existrovat další nebo jejich kombinace. Všechny historické údaje pochází z [1]. 1.1 Typy překladačů Za dobu vývoje překladačů vznikly jejich různé druhy, které bych zde chtěl stručně popsat. Překladače se dají rozdělit na dvě velké skupiny, podle toho, co je výsledkem jejich provedení. Při následujícím rozdělení jsem se držel terminologie použité v [2]. kompilační překladač Jedná se o překladače jejichž výsledkem je výstupní kód. Kompilační překladač tedy přijímá zdrojový kód ve zdrojovém jazyce a vytváří výstupní kód v jazyce výstupním, jako výsledek překladu. Vygenerovaný kód jsme pak, po případných úpravách, schopni spustit. Tím dochází k oddělení doby překladu a doby běhu programu. Program spustíme vícekrát než ho kompilujeme. Na tom zakládají optimalizace, protože vícenásobným spouštěním se kvalita výstupního kódu projeví více, než případný delší překlad. 2

Podkapitola 1.2. Shrnutí úvodu interpretační překladač Zcela se liší od překladačů kompilačních, protože negenerují výstupní kód. Namísto toho přijatý zdrojový kód ve zdrojovém jazyce přeloží tak, že je vykonán, jako kdyby byl spuštěn výstupní kód kompilačního překladače. Překladač interpretuje zdrojový kód. Protože dochází k spouštění programu, tak musíme mimo vstupního kódu připravit také vstupní data. Výstupem je pak výstup vytvořený spuštěním programu. U tohoto překladače se počet spuštění rovná počtu překladů. Tím se optimalizacemi nevyplatí zabývat a předpokládáme, že vstupní kód je již co nejvíce optimální. Mimo tohoto rozdělení můžeme překladače rozdělit podle způsobu komunikace s uživatelem. Překladače nazýváme konverzačními, pokud má uživatel provádějící překlad možnost zasahovat do chodu překladače konverzovat s ním. Druhým druhem jsou klasické, které nám neumožňují jakýkoli zásah do překladu. Dále existují ještě speciální druhy překladačů jako např. cross-compiler, který překládá pro jinou architekturu než na které běží, nebo různé překladače, které se sami kompilují. Samozřejmě existují i další, ale toto byly ty nejvýznamnější zástupci. Jak vidíme, tak je poměrně veliké množství různých druhů překladačů. V této práci se pojmem překladač myslí klasický kompilační překladač dle definic výše. Ostatními se v této práci nezabývám, protože zde nejsou relevantní. 1.2 Shrnutí úvodu Pokud chceme realizovat složitější, čitelné a přenositelné programy, tak je musíme napsat nějakém z vyšších programovacích jazyků. Tato souvislost nese s sebou nutnost tvorby kvalitních a efektivně běžících překladačů s optimalizací výstupního kódu. Hlavním cílem práce je ukázat návrhem překladače s optimalizací výstupního kódu a metod použitých při jeho tvorbě. Výsledkem je implementace překladače vytvořeného dle popisu v této práci. Znalost fungování překladače navíc každému programátorovi ve vyšším programovacím jazyce, který je tím vázán na překladač, umožní mnohem lépe řešit případné problémy a chyby při překladu. Z tohoto důvodu by měl každý programátor ve vyšším programovacím jazyce rozumět konstrukci a fungování překladače (nebo alespoň znát základní principy). 3

4

Kapitola 2 Teoretické základy V této kapitole proberu různé definice a zavedení pojmů, které budu požívat dále v textu. Určitě bych tuto kapitolu nebral jako referenci pojmů teoretické informatiky, protože se zde snažím spíš popsat důležité pojmy, které používám v této práci. Pojmy nejsou tedy úplně popsány, ale jen v rámci toho, jak je potřebujeme. Při popisu teorie grafů je použil zejména literaturu [3]. Pro popis všech záležitostí okolo překladačů jsem používal hlavně [2]. 2.1 Grafy Graf definujeme jako trojici (U, H, ), kde U je množinou uzlů, H je množinou hran. Zobrazení incidence nám přiřazuje každé hraně h H neuspořádanou dvojici [u, v], kde u, v U. Znamená to, že z u do v vede hrana a obráceně. Definicí incidence jako neuspořádané dvojice jsme získali neorientovaný graf. Orientovaný graf získáme přidáním orientace hranám. To zařídíme změnou zobrazení incidence. U orientovaného grafu změníme definici na : h (u, v). Tím, že jsme přiřadili hraně uspořádanou dvojici, jsme stanovili pořadí kde první je uzel ze kterého hrana vychází a druhý je ten do kterého hrana vede. V tomto případě vede orientovaná hrana z uzlu u do v. Tím jsme získali definici orientovaného grafu. 2.1.1 Průchod grafu do hloubky Při průchodu do hloubky procházíme uzly grafu dokud můžeme. Potom se vracíme k již komplentě neprojetých uzlů kde provádíme totéž. K uložení informace kam se máme vrátit potřebujeme zásobní. 2.1.2 Stomy Jedná se speciální formu grafu, ve kterém je U 1 počtem hran. Graf je dále acyklický. Strom obsahuje kořen, do kterého nevstupují žádné hrany a listy, z kterých žádné hrany nevystupují. Můžeme definovat následující průchody jejich struktury. Předpokládejme že máme binární strom s uzlem u, levý a pravý podstrom. Na tom založime popis metod průchodu. Průchod preorder Nejdříve projdeme uzel u, levý podstrom a pak pravý podstrom. Průchod postorder Průchod začínáme levým podstromem, pravým podstrom a závěrem navštívíme uzel u. 5

Kapitola 2. Teoretické základy Průchod inorder Nejdříve projdeme levý podstrom, pak uzel u a závěrem pravý podstrom. 2.2 Formální jazyky Než definujeme formální jazyky, tak musíme definovat co to je abeceda. Abeceda je nepráznou množinou symbolů. Rětězec nad abecedou je zřetězení posloupnosti sybolů abecedy. Prázdný řetěz označujeme symbolem ε. Formální jazyk je definován jako podmnožina všech řetězů vytvořených na dané abecedě (tedy T ). 2.3 Gramatiky Gramatika definována jako uspořádaná čtveřice G = (N, T, P, S), kde N jsou neterminání symboly, T jsou symboly terminální, P jsou pravidla gramatiky a S je počáteční symbol gramatiky. Musí dále platit, že N T = a S N. Pravidla se skládají z levé a pravé strany. Tyto jsou složeny zřetězením symbolů N a T, které budeme označovat řeckými písmeny. Pravidla zapisujeme ve formě α β, jež tvoří uspořádanou dvojici (α, β) P. Bezkontextové gramatiky jsou takové gramatiky, které mají pravou stranu pravidla P tvořenou jedním neterminálem. Přímou derivací na gramatice myslíme nahrazení neterminálu pravé strany pravidla pravou stranou s nahrazovaným neterminálem na levé straně. Provádím náhradu náhradu X v α γxδ, pak ho nahradíme nějakou pravou stranou X σ na α γσδ. Zřetězení symbolů γ a δ zde tvoří okolí neterminálu. Konečně ješté zbývá definovat, jaký jazyk nám gramatika generuje. Jazyk generovaný gramatikou je reprezentován množinou L(G) = {x : x T S x}. 2.3.1 Regulární gramatiky Regulární gramatiky jsou speciálním případem bezkontextových gramatik. Všechny pravidla gramatiky musí mít tvar A a, A ab, kde A, B N a a T. Výhodou regulárních gramatik je, že se u nich dá velice jednoduše implementovat syntaktická analýza. Provádíme ji deterministickým automatem, který umíme dobře implementovat. Pravidla A a nám naznačují přechod ze stavu A do koncového stavu (přijetí automatem), pokud je na vstupu symbol a. Pravidlo A ab provede přechod automatu ze stavu A do stavu B při přečtení symbolu a na vstupu. 2.3.2 Překladové gramatiky Překladovou gramatiku získáme přidáním výstupních symbolů do definice obecné gramatiky. Tyto symboly jsou pak umístěny v pravých stranách pravidel a tvoří tím výstup překladače v daném místě. Vytvářejí tím překlad. Takto lze přovádět jednoduché překlady, které jsou syntaxí řízené, generující přímo výstupní kód překladače nebo jiný meziprodukt, který bude dále zpracováván. 2.3.3 Atributované gramatiky Atributové gramatiky získáme, když terminálům a neterminálům přiřádíme atributy tedy parametry pro uložení informací. Hlavním významem atributů v gramatice, je zaručení přesunu informací po derivačním stromě. Syntetizované atributy nám zaručují 6

Podkapitola 2.4. Končené automaty přesun informací nahoru. Druhým typem atributů jsou dědičné, které nám umožňují přesun informací směrem dolů po derivačním stromu. Atributy speciálních symbolů Lexikální symboly mají buďto syntetizované atributy nebo žádné. Jelikož se jendá o terminály, tak nemá význam dávat dědičné atributy, protože nic není niž v derivačním stromu než terminály. Příkladem můžou být číslicové literály, které obsahují syntetizovaný atribut s hodnotou vypočítanou při lexikální analýze. Dále můžeme zavést tzv. sémantické neterminály. Tyto neterminály nederivují žádnou pravou stranu, ale provádějí sémantickou akci. Provádí nějakou činnost v libovolném místě pravé strany pravidla. Parametry pro provedení získává jako dědičné atributy. Tím se velice podpobá volání procedury vyššího programovacího jazyka. 2.4 Končené automaty Konečný automat je definován pěticí (Q, T, δ, q 0, F), kde Q je množina stavů, T je vstupní abeceda, q 0 je počáteční stav a F je množina stavů koncových. Dále platí F Q a q 0 Q. Přechodová funkce δ nám přiřazuje každé dvojici (q, t) všechny podmnožiny Q, kde q Q a t T. Takto je definovaný nedeterministický konečný automat. Konfiguraci konečného automatu definujeme uspořádanou dvojici (q, t). Konečný automat nazýváme deterministickým, když přechodová funkce přiřazuje dvojici (q, t) jeden nebo žádný stav q x Q. Takto definovaný konečný automat jsme již schopni implementovat, protože každý přechod automatu je jednoznačný. Navíc se dá každý nedeterministický konečný automat převést na ekvivalentní deterministický [3, str. 178]. Může ale dojít k výraznému zvýšení počtu stavů. Deterministický konečný automat můžeme implementovat pomocí tabulky přechodů, nebo 2.4.1 Regulární výrazy a gramatiky Regulární výrazy jsou speciálním úsporným jazykem pro zápis předpisů pro syntaxi jazyku bez použití regulárních gramatik. Tyto výrazy můžeme převést na regulární gramatiku. Jelikož víme, že ke každé regulární gramatice existuje deterministický konečný automat a opačně (viz [2, str. 50 53]), tak jsme je schopni efektivně implementovat. 2.5 Zásobníkové automaty Zásobníkové automaty jsou klasické automaty rozšířené o nekonečný zásobní. Navíc jsou přidány zásobníkové symboly. Nyní záleží přijetí automatem ještě na stavu zásobníku. Abychom jej mohli implementovat, tak musí být deterministický. Příkladem jsou syntaktické analyzátory. 2.6 Syntaktická analýza Syntaktická analýza nám dává jako výsledek informaci, jestli předložený zdrojový text spadá do množiny všech řetězů, které se dají pomocí gramatiky vytvořit. Testování se provádí na základě sestavení derivačního stromu. Každé pravidlo gramatiky nám 7

Kapitola 2. Teoretické základy popisuje, jak má vypadat uzel a varianty jeho možných potomků (pravé strany pravidel). Pokud se nám povede sestrojit z těchto, gramatikou definovaných, uzlů derivační strom, tak je vstupní text syntakticky správný. V případě, že se nepovedlo derivační strom sestavit, pak analyzovaný vstupní text syntakticky správný není. Vyřešení sestavení derivačního stromu, je úkolem algoritmů syntaktické analýzy. Algoritmy syntaktické analýzy můžeme rozdělit do dvou skupin podle toho, jakým směrem vytváří derivační strom. 1. Top-Down algoritmy sestavují derivační strom od kořene k listům. 2. Bottom-Up algoritmy sestavují derivační strom od spodu k kořeni. Nejvýznamější Bottom-Up algoritmy jsou LR(k), kde k udává počet symbolů, o které nahlíží dopředu, aby se mohl lépe rozhodnout. Tyto analyzátory jsou založené na operacích shift a reduce. Pomocí operací shift se snažíme sestavit na pravou stranu pravidla. Pokud se nám povelo tuto pravou stranu sestavit, tak můžeme rozpoznaou stranu redukovat na levou stranu pravidla. Takto se postupně dopracujeme k tomu, že dostaneme jako výsledek redukce kořen derivačního stromu a zjistili jsme, že syntaxe je správná. LR znamená že čteme vstup zleva (L) a že derivujeme nejpravější derivaci (R). LR analyzátory umí např. rychleji objevit syntaktické chyby a umí pokrýt velký počet různých gramatik, ale mají tu velkou nevýhodu, že již při nevině vypadajících gramatikách je jejich návrh poměrně náročný. Proto se s nimy většinou setkáváme ve spojení s generátory syntaktických analyzátorů. V této práci se dále s nimy nebudu zabývat, protože u implementovaného překladaće je syntaktický analyzátor realizován LL(1), který je nejznámějším zástupcem Top-Down algoritmů syntaktické analýzy. LR analyzátory jsou natlik významné, že jsem se zde o nich chtěl alspoň zmínit. 2.6.1 LL(1) syntaktický analyzátor LL(1) syntaktický analyzátor je realizován jako zásobníkový automat. Stav je jenom jeden, který je také stavem koncovým. Při přechodech se modifikuje jen obsah zásobníku, jehož obsah rozhoduje o přijetí vstupního řetězu automatem. Zásobníkovou abecedu tvoří terminály a neterminály. Počátečním zásobníkovým symbolem je levá strana, tedy neterminál, počátečního pravidla gramatiky. Náhledový symbol (angl. lookahead) nám reprezentuje první nezpracovaný symbol vstupu. Vstup je zpracováván zleva doprava (první L) a vždy se derivuje nejlevější derivace (druhé L). Náhledový symbol je jeden. Odtud název LL(1). Analýza probíhá tak, že se analyzátor rozhoduje na základě vrcholu zásobníku. Pokud tam je neterminál, tak provedeme expanzi neterminálu, tedy nahradíme symbol neterminálu na vrcholu jeho pravou sranou, kterou vybereme na základě symbolu náhledu. Jinak musí být na zásobníku terminál, který je porovnán s symbolem náhledu. Tuto operaci nazýváme srovnání (angl. match). Po úspěšném srovnání symbol odstraníme z vrcholu. Překlad končí prázdným zásobníkem nebo syntaktickou chybou. Prakticky je funkce analyzátoru založena na průchodu derivačního stromu do hloubky. Pokud se nám povede projít strom, tak půjde i sestavit a syntaxe analyzovaného vstupu, definovaná gramatikou, tím bude správná. Procházení vstupního textu je zařizeno směrem čtení vstupního řetězu. Vždy se provádí nejlevější derivace, protože pokud dojde k expanzi, tak se expanduje neterminál na vrcholu zásobníku, 8

Podkapitola 2.7. Definice programovacího jazyka K syntaktické chybě může dojít při srovnání s odlišným symbolem než je na vrcholu, nebo pokud při expanzi nelze vybrat pravou stranu pro doplnění. Syntaktický analyzátor nemůže dále pokračovat. Pak záleží na tom, jestli je implementováno zotavení z syntaktických chyb. Analyzátor se může pokusit chybu opravit, nebo jistou část vstupu přeskočit a zkusit pokračovat dál v analýze. Rozkladová tabulka a její návrh LL(1) analyzátory pokrývají podmnožinu bezkontextových gramatik. Z toho důvodu musíme vždy rozhodnout, jestli z dané gramatiky půjde tento typ analyzátoru zkonstruovat. Oproti LR analyzátorů je tato podmnožina gramatik, ze kterých se dá realizovat LL(1) analyzátor, opravdu malá. Aby byl LL(1) analyzátor z dané gramatiky realizovatelný, tak musí být splněny následující vlastnosti. 1. Všechny pravé strany jednoho pravidla musí mít různé množiny first. 2. Pokud pravidlo může skončit derivací prázného řetězce, pak je nutné, aby platilo 3. Pravidla gramatiky nesmí obsahovat pravou rekurzi. První dvě pravidla slouží pro výběr správné pravé strany pro expanzi, na základě náhledového symbolu. Třetí pravidlo by způsobilo zacyklení analyzátoru, protože by se stále snažil expandovat neterminál, který by znovu expandoval na sám sebe. Implementace LL(1) analyzátoru Jelikož se jedná o implementaci zásobníkového automatu, tak musíme rozhodnout, jak zásobník implementujeme. Můžeme použít zásobník volání programu nebo explicitní zásobník, realizovaný dle vlastních požadavků. Podle implementace zásobníku získáváme dvě hlavní metody implementace LL(1) analyzátoru. 1. Rekurzivní sestup. Používá se implementace programového zásobníku volání. Neterminály jsou reprezentovány podprogramy a v jejich těle je zakódován právě jeden řádek rozkladové tabulky. 2. Tabulková implementace. Uživaný zásobník je explicitní. Rozkladová tabulka je většinou realizována pomocí polí. U obou implementací si navíc musíme pamatovat náhledový symbol. 2.7 Definice programovacího jazyka Pro definici jazyka potřebujeme nejdříve vědět, jak má daný jazyk vypadat. Musíme tedy popsat jeho syntaxi. Jak již víme, tak nejlepší je syntaxi popsat pomocí bezkontextových gramatik, protože pro ty máme již efektivní algoritmy pro provedení syntaktické analýzy. Musíme tedy navrhnout bezkontextovou gramatiku, pokud možno hned upravenou pro používaný algoritmus syntaktické analýzy. Následkem použití bezkontextových gramatik je, že ztrácíme kontrolu kontexových podmínek, které nejsou tímto druhem gramatik zachtitelné. Pro přidání kontextové kontroly zavádíme tzv. statickou sémantiku. Statická sématnika nám popisuje kontextové podmínky, které musí být splněny pro správnost vstupního jazyka. Kontrola 9

Kapitola 2. Teoretické základy se většinou provádí při syntaktické analýze. Příkladem může být např. preinkrement operátor ++ v jazyce C. Ve staticke sémantice se musí zaručit, že za operátorem musí být identifikátor proměnné. Nyní již zbývá jen popsat dynamickou sémantiku. Dynamická sémantika ná popisuje, jaký je význam jednotlivých konstruktů, které lze vytvořit na základě gramatiky jazyka. Slouží proto, aby jsme věděli, co bude daný výraz dělat. Někdy také bývá uvolněna obecnější gramatika, která popisuje nadmnožinu definovaného jazyka. Detaily ztracené tímto zobecněním, jsou pak dolpněna do seznamu pravidel statické sémantiky. Tím jsme zachovali správnost definice jazyka, která by jinak byla narušena. Můžeme tedy přesunou některé detaily do vyhodnocení sémantiky, ze které se syntaktický analyzátor lépe zotavuje, než ze syntaktických chyb. Podle této sekce byl navrhnut jazyk implementovaného překladače. Gramatiku, statickou sémantiku a dynamickou sémantiku najdete v příloze??. Navíc tam najdete definici tokenů lexikálního analyzátoru. 10

Kapitola Řešený problém, cíle, struktura práce 3 Práce řeší návrh a implementaci překladače s optimalizátorem, používajícího v zadání vypíchnuté optimalizační metody. To, jaký bude vstupní jazyk a další detaily jsou na autorovi resp. na tom, jak se s vedoucím práce domluvíme. Jako vstupní jazyk jsem zvolil zjednodušený jazyk C, který je velice rozšířen. Navíc patří spolu s C++ k mým oblíbeným programovacím jazykům. Zjednodušení bylo nutné, protože implementace kompletního překladače jazyka C v plné formě by nebylo stihnutelné. Hlavní složitost je způsobená velikou rozmanitostí datových typů, převodů mezi sebou a jejich zavádění (deklarace, definice). Existoje veliké množství jazyků, jejichž základ tvoří syntaxe jazyka C. Příklady jsou např. Java, C#, atd. I když jsou tyto jazyky poměrně rozdílné, tak je zachována základní syntaxe a filozofie jazyka C. Důsledek toho je, že každý kdo zná nějaký z těchto jazyků, bude rozumět, nebo bude mít alespoň tušení, co bude program dělat. Tím, že jazyky založené na jazyce C jsou velice rozšířené, bude také mnoho lidí rozumět vstupnímu jazyku. 3.1 Stanovení cílů Proto, abychom věděli jaké jsou hlavní cíle. Splnění těchto cílů, je zhodnoceno v závěru. Následující cíle shrnují požadavky a cíle, které vyplývají z zadání práce nebo z mých požadavků. Stěžejními cíly je: C1. Implementovat překladač z zjednodušeného jazyka C do jazyka symbolických instrukcí Java Virtual Machine. C2. Implementovat optimalizaci spojováním konstant. C3. Implementovat optimlizaci odstraněním mrtvého kódu. C4. Implementovat optimalizaci přímým vkládáním funkcí na základě interprocedurální analýzy. C5. Provést testy překladače a implementovaných optimalizací. C6. Porovnat vliv optimalizací na výslednou rychlost programu. C7. Výše uvedené cíle se snažit splnit co nejkvalitněji (v rámci možností). Nyní by chtělo specifikovat další požadavky, které by měly být splněny při realizaci výše vytyčených cílů. Požadavky se rozdělují na funkční, tedy ty kterou souvisí funkcionalitou projektu a nefunční, které specifikují ostatní požadavky na projekt. Toto 11

Kapitola 3. Řešený problém, cíle, struktura práce rozdělení jsem zvolil, protože se běžně používá v softwarovém inženýrství. V seznamu je typ poznamenán v závorce za popisem požadavku. Požadavky jsou: P1. Zkompilovaný program by měl být spustitelný a použitelný stejným způsobem, jako kdyby byl přeložen překladačem javac. (funkční) P2. Uživatel může rozhodnout, jaké optimalizace chce provést. (funkční) P3. Zkompilovaný kód by měl bý použitelný jako knihovna. (nefunkční) P4. Kód překladače by měl být napsán co nejelegantněji a nejefektivněji. (nefunkční) U posledního požadavku PP4.. bude sice těžké dokázat jeho splnění, protože existuje vždy více řešení a ne vždy je zcela jasné jaká bude výsledně lepší. Navíc každé řešení je jistým způsobem kompromisem. Z toho důvodu by chtělo tento požadavek brát spíše jako ideál, kterému se budeme snažit co nejvíce přiblížit. Jak vidíme ze seznamu požadavků, tak obsahuje jen jeden funční požadavek. Funční požadavky totiž souvisejí s případy užití (angl. Use Case), kterè jsou v případě tohoto projektu velice jednoduché. Máme jen jednoho aktéra (uživale), který může provádět jen překlad resp. překlad částečně ovlivnit (zapnutí optimalizací, atd.). Z toho důvodu nemá cenu vytvářet digram případu užití, jelikož je tak jednoduchý a jasný. 3.2 Struktura práce Práce by se dala rozdělit na několik hlavních částí. Číslování v seznamu neodpovídá číslování kapitol nebo jiných objektů práce. 1. Úvodní část Práce začíná kapitolou Úvod, ve které popisujeme opodstatnění tvorby překladačů a proč je dobré znát jejich konstrukci. Řeší se potřeba optimalizátorů, rozdělení typů překladačů a také stručné popisy historického pozadí. Poté následuje kapitola Teoretické základy, kde budou zavedeny všechny pojmy používané v práci. 2. Analytická část Třetí kapitolou je tato, a popisuje řešený problém (rozhodnutí) tak, že čtenář by měl vědět co se bude dělat. Dále jsou zde specifikovány cíle, jejichž splnění je cílem práce. Následuje popis rozdělení práce. Čtvrtá kapitola popisuje srovnání již existujících implementací, jako inspiraci nebo výchozí bod pro vlastní řešení. 3. Implementační část Popis implementace je probírán v kapitolách Architektura překladaće, Pŕední část a zadní část. Popsáno je, jak byla vyřešena implementace navržená v předchozích částech práce. 4. Testováni Celé testování a jeho metodika je popsána v kapitole Test. 5. Závěr 6. Přílohy Struktura práce odpovídá doporučené struktuře implementační bakalářské práce uvedené na informačních stránkach katedry počítačů. <info.felk.cvut.cz> 12

Kapitola Analýza existujících implementací 4 V této kapitole bych chtěl probrat existující projekty a metody pro řešení překladu z nějakého zdrojového jazyka do bytekódu Java Virtual Machine. Některé probírané postupy jsou platné nejen pro JVM, ale i pro další výstupní jazyky a formáty. Jelikož doprovodný program je překladač jazyka C, tak se zaměřím zejména na realizace překladačů jazyka C. Těžištěm je seznámit čtenáře s existujícími projekty, které řeší zhruba stejný problém jako tato práce. Veškeré informace jsem získal z Wikipedie [4] nebo z domovských stránek příslušných projektů. Projekty budou popsány jen velice stručně s vypíchnutím jejich výhod a nevýhod. Důvodem tohoto pojetí je, že s projekty jako LLVM by se dala napsat celá další práce nebo dokonce rozsáhlejší kniha. 4.1 NestedVM NestedVM [5] je nástroj, který umožňuje konverzi zkompilovaného kódu pro MIPS přeložit do bytekódu pro JVM. Nejdříve se provede kompilace zdrojových kódů pomocí GCC do MIPS binárky. Tato binárka je následně přeložena do bytekódu Java Virtual Machine. NestedVM je opensource software šířený pod Apache 2.0 licencí. Výhodou je, že můžeme překládat zdrojové kódy všech jazyků, které jsou podporovány překladačem pro překlad do binárky MIPS. Další výhodou je, že můžeme využívat všech možností daného překladače. NestedVM je pracuje s překladačem GCC 3.3.6. Nevýhoda je v tom, že není podpora novějších verzí GCC. 4.2 LLVM LLVM [6] je celá infrastruktura pro implementaci překladačů. Převzaty jsou GCC frontendy a je k dispozici široký výběr doplňků, které můžeme využít pro vývoj vlastních překladačů. Celý systém LLVM je založen na jazyce virtuálního počítače LLVM IR (LLVM Internal Representation). Tento jazyk je velice dobře navrhnutý a optimalizovatelný. Je velice podobný n-adresovému kódu. Jazyk LLVM IR je přeložitelný do jiných jazyků jako např. Jasmin. LLVM IR je buďto spustitelný interpretem LLVM, nebo přeložitelný do nativního kódu počítače, na kterém je překládáno. Toto je umožněno, protože interpret LLVM při spuštění programu provede JIT kompilaci. Tedy přeloží LLVM IR do nativního kódu počítače na němž je spuštěn. 13

Kapitola 4. Analýza existujících implementací LLVM je překladačem a interpretem. Navíc ještě umožňuje jednoduché použití jeho součástí ve vlastních překladačích. Pokud tedy potřebujeme vytvořit nějaký překladač, tak můžeme jen napsat přední část generující jako vnitřní formu jazyk LLVM IR. Zbytek bychom přenechali zadní části, která je velice kvalitní. Testy na [6] ukázaly, že optimalizátor je tak kvalitní, že v testech silně poráží implementaci překladače GCC při stejných optimalizacích. Výhodou je široká nabídka různých kvalitních součástí, které můžeme použít při vlastním vývoji překladače. Projekt a jeho součásti jsou velice dobře dokumentovány. Nevýhodou může být pro začátečníka proniknutí masou nástrojů, modulů a možností. 4.3 C2J C2J [7] je projekt, který realizuje překlad z jazyka C do jazyka Java. Projekt je založen na překladači GCC. Překladačem je nejdříve je generován javovský kód, pak je teprve z něho vygenerová bytekód pro JVM. V podstatě se provádí klasický překlad mezi dvěma jazyky, následovaný kompilací výsledného kódu. Výsledný bytekód je uložen do *.class souboru. Překladač plně podporuje ANSI C knihovnu, ANSI C a K&R C. Tato dobrá podpora je asi největší výhodou tohoto projektu. 4.4 Java GCC Backend Java GCC Backend [8] je projekt implementující zadní část překladače GCC pro překlad do jazyka symbolických instrukcí Jasmin. Assembler jasmin vygeneruje výsledný spustitelný program pro JVM. Projekt je nejstarším pokusem o překlad jazyka C do Java bytekódu. Jde tedy jen o rozšíření překladače GCC. Výhodou je, že se práce s takovým překladačem vůbec neliší od standardní práce s GCC. Využitím předních částí GCC, rozumí takto vytvořený překladač všem jazyků, kterým rozumí GCC. Můžeme používat všechny možnosti kompilátoru GCC, i když některé možnosti nebude možno používat, protože nedávají pro danou situaci smysl. Navíc se dosahuje podle [8] docela dobrých výsledků s přeloženým kódem. 4.5 Jiná řešení Pokud chceme překládat z nějakého nestandardního jazyka, nebo jeho podmnožiny, tak jsme nuceni si vytvořit specializovanou přední část překladače. Pokud chceme ještě nějaké speciality v zadní části, tak si musíme ji napsat také sami. Většinou necháme zadní část překladače generovat zdrojový kód v jazyce symbolických adres pro architekturu, pro kterou překládáme. Máme k dispozici hned několik assemblerů, které se nabízí pro sestavení výsledného spustitelného souboru. O tento typ překladače se jedná i doprovodného projektu této práce. Překládá se z podmnožiny jazyka C do java assembleru Jasmin. Assembler jasmin nám pak přeloží do java bytekódu. 14

4.6 Shrnutí a zhodnocení Podkapitola 4.6. Shrnutí a zhodnocení Pokud potřebujeme překládat z nějakého standardního jazyka v jeho plném rozsahu, tak se vyplatí použít nějaký z výše uvedených projektů. Nejspíš bych si v takovém případě vybral projekt LLVM, do kterého se dobře zasahuje a má dobrou dokumentaci. Když chceme implementovat nějaký nestadardní jazyk tak musíme si překladač, nebo jeho část napsat sami. Použít se dá také některých nástrojů z výše uvedených projektů. 15

16

Kapitola 5 Architektura překladače Každý překladač se většinou skládá ze čtyrech základních částí. Tyto části příjímají určitý vstup v jisté formě, formátovaný dle pevně daných pravidel, na jehož základě daná část provádí svou činnost. Výsledkem chodu dané části je výstup, který byl vytvořen operacemi na základě vstupních dat této části. Výstup je pak buďto dále zpracován následující částí, nebo pokud se jedná o poslední část, již výsledným překladem. Obecná architektura překladače je znázorněna na obrázku 5.1. Obrázek 5.1: Obecná architektura překladače. Šipky nám ukazjí směr toku dat mezi jednotlivými částmi. Pokud dojde k vynechání části, tak je chování takové, že to co dostává na vstup jen přeposílá na výstup pro zpracování dalšími. Jakou formou tok informací probíhá záleží na tom, jakým způsobem jsou realizovány jednotlivé části. Formou můžou být soubory, pipy, fronty nebo jiné datové struktury, pomocí kterých se dá výstup předat tak, aby bylo možné tyto data co nejefektivněji zpracovat v další části. Detialy k realizaci jednotlivých částí bude v následující sekci 5.1. Asi nejproblematičtější je přesun informací z části preprocessingu do přední časti překladače, protože může být zatížen poměrně velkým počtem chyb jak syntaktických, tak sémantických. Přední část se musí s těmito možnými nesrovnalostmi vstupu vyrovnat a dát programátorovi co nejpřesnější popis, pokud možno všech, chyb. Navíc po provedení části preprocessingu se znemožní přesné mapování symbolů vstupu přední části a symboly zdrojového souboru. Důvodem jsou změny zdrojového kódu preprocessorem, který jej může změnit k nepoznání. Ten samý problém se nastává u varování, které jsou stejného charakteru. 17

Kapitola 5. Architektura překladače Tyto problémy u ostatních toků dat nenastávají, protože se předpokládá, že budou obsahově koretní. Je to důsledek řetezcového propojení jednotlivých částí architektury. Pokud dojde k zjištění nějaké chyby vstupu přední části, tak se nebude výstup posílat dalším částem řetězu zpracování. Chyba znemožní sestavení dat pro další část a neumožní tím běh všech následujících částí. Překlad pak končí s chybou. Samozřejmě, že ne všechny části jsou součástí každého překladače, ale převážná většina překladačů obsahuje alespoň ty části, kterou jsou ve schématu orámové plnou tlustou čarou. Zejména je tím myšlena část první a poslední, jejichž provedení může být volitelné proto jsou orámovány šrafovaně. Také je nutné říci, že u složitějších překladačů se tomuto rozdělení prostě nevyhneme. Znázorněné rozdělení potvrzuje většina dnešních překladačů, které obsahují v jisté podobě všechny tyto části. 5.1 Části překladače Rozdělení překladače do několika částí má mnoho výhod. Části jsou na pracují na sobě relativně nezávisle a umožnují tím dekompozici problému překladu na několik dílčích částí. Tím se při vývoji můžeme soustředit na danou část, což vede k zlepšení kvality a čitelnosti zdrojového kódu překladače. Dále se tím také zlepší rozšiřitelnost, protože stačí je rozšiřovat nebo nahrazovat je jednu dílčí část. I přes toto rozdělení mohou být části Co se týče implementace jednotlivých částí, tak může být velice různorodá. Někdy bývají všechny části schované v jednom programu jako jeho moduly, jindy jsou části paralelně běžící procesy, které jsou synchonizovány vstupem předchozí části. 5.1.1 Preprocessing Preprocessing, nebo-li volně přeloženo předpřípravná, je částí, ve které se provádí transformace na zdrojovém kódu pro překladač. Transformace mohou být provedeny různými separátními programy resp. skripty nebo mohou být přímo zabudovány do překladače. Důležité je, že výsledkem této části je připravený vstup, který je zpracovatelný přední částí překladače. Tím bezkonflikně můžeme rozšířit libovolný jazyk, bez zásahu do jeho překladače. Velikou výhodou tohoto přístupu je, že takto rozšířené jazyky jsou kompatibilní se všemi překladači pro daný jazyk. Typickým příkladem překladače používající tuto fázi je překladač jazyka C resp. C++. Preprocessor jazyka C vkládá soubory, spravuje a expanduje makra, řídí podmíněný překlad, atd. Dalším příkladem je framework Qt, který před vlastním překladem provede speciální překompilátor Meta Object Compiler. Ten zaručí převod specifik používaných v Qtdo standardního jazyka C++, který je pak již přeložitelný programátorovým oblíbeným překladačem. Pokud provádíme tuto fázi ručně, tak si musíme dát pozor, abychom nezavlekli další chyby do zdrojového kódu. Při chybách v zdrojovém kódu nám v závislosti na závažnosti změn v předpřípravě může dojít k ztrátě lokalizace chyb. Tyto chyby je pak velice těžké vystopovat. 5.1.2 Přední čast překladače Přední čast, nebo anglicky frontend, zpracovává zdrojový text a generuje vnitřní formu IR pro zadní čast překladače. Vnitřní formou může být abstraktní syntaktický strom, n-adresový kód nebo jiný vnitřní jazyk, který pak bude dobře optimalizovatelný a bude se z něj dobře generovat výstupní kód. 18