Analizator syntaktyczny (parser) jest funkcja

Podobne dokumenty
Metody Realizacji Języków Programowania

Metody Realizacji Języków Programowania

Metody Realizacji Języków Programowania

Metody Realizacji Języków Programowania

Metody Realizacji Języków Programowania

Metody Kompilacji Wykład 8 Analiza Syntaktyczna cd. Włodzimierz Bielecki WI ZUT

Wykład 5. Jan Pustelnik

Metody Kompilacji Wykład 7 Analiza Syntaktyczna

Gramatyki atrybutywne

Architektura komputerów. Asembler procesorów rodziny x86

10. Translacja sterowana składnią i YACC

Metody Kompilacji Wykład 1 Wstęp

Parsery LL(1) Teoria kompilacji. Dr inż. Janusz Majewski Katedra Informatyki

Podstawy programowania. Wykład Funkcje. Krzysztof Banaś Podstawy programowania 1

METODY I JĘZYKI PROGRAMOWANIA PROGRAMOWANIE STRUKTURALNE. Wykład 02

Analiza semantyczna. Gramatyka atrybutywna

Metody Kompilacji Wykład 3

Metody Realizacji Języków Programowania

Architektura komputerów

Analiza leksykalna 1. Teoria kompilacji. Dr inż. Janusz Majewski Katedra Informatyki

Laboratorium 03: Podstawowe konstrukcje w języku Java [2h]

Język programowania: Lista instrukcji (IL Instruction List)

Matematyczne Podstawy Informatyki

4 Literatura. c Dr inż. Ignacy Pardyka (Inf.UJK) ASK MP.01 Rok akad. 2011/ / 24

Programowanie Niskopoziomowe

0.1 Lewostronna rekurencja

Zmienne, stałe i operatory

Instrukcja do ćwiczenia P4 Analiza semantyczna i generowanie kodu Język: Ada

Język ludzki kod maszynowy

Języki programowania zasady ich tworzenia

Paradygmaty programowania

Podstawy programowania w języku C i C++

Przyczyny dwustopniowego tłumaczenia

Metody Realizacji Języków Programowania

Podstawy Programowania C++

Analizator syntaktyczny

Struktura i działanie jednostki centralnej

2.2. Gramatyki, wyprowadzenia, hierarchia Chomsky'ego

Ćwiczenie nr 3. Wyświetlanie i wczytywanie danych

Języki formalne i automaty Ćwiczenia 4

Zadanie analizy leksykalnej

Programowanie w C++ Wykład 2. Katarzyna Grzelak. 4 marca K.Grzelak (Wykład 1) Programowanie w C++ 1 / 44

Generatory analizatorów

Efektywna analiza składniowa GBK

Język JAVA podstawy. Wykład 3, część 3. Jacek Rumiński. Politechnika Gdańska, Inżynieria Biomedyczna

Programowanie Niskopoziomowe

KONSTRUKCJA KOMPILATORÓW

Języki i paradygmaty programowania

Podstawy programowania skrót z wykładów:

MOŻLIWOŚCI PROGRAMOWE MIKROPROCESORÓW

Dr inż. Grażyna KRUPIŃSKA. D-10 pokój 227 WYKŁAD 7 WSTĘP DO INFORMATYKI

Strona główna. Strona tytułowa. Programowanie. Spis treści. Sobera Jolanta Strona 1 z 26. Powrót. Full Screen. Zamknij.

Java EE produkcja oprogramowania

1 Podstawy c++ w pigułce.

Programowanie Niskopoziomowe

Programowanie komputerowe. Zajęcia 3

Odczyt danych z klawiatury Operatory w Javie

Języki formalne i techniki translacji

Wstęp do Programowania, laboratorium 02

C++ - przeciążanie operatorów. C++ - przeciążanie operatorów. C++ - przeciążanie operatorów. C++ - przeciążanie operatorów

Instrukcja do ćwiczeń nr 4 typy i rodzaje zmiennych w języku C dla AVR, oraz ich deklarowanie, oraz podstawowe operatory

Język C zajęcia nr 11. Funkcje

Mikrokontroler ATmega32. Język symboliczny

Operatory. Operatory bitowe i uzupełnienie informacji o pozostałych operatorach. Programowanie Proceduralne 1

Poprawność semantyczna

JIP. Analiza składni, gramatyki

Informatyka I. Klasy i obiekty. Podstawy programowania obiektowego. dr inż. Andrzej Czerepicki. Politechnika Warszawska Wydział Transportu 2018

Języki formalne i automaty Ćwiczenia 1

Języki programowania C i C++ Wykład: Typy zmiennych c.d. Operatory Funkcje. dr Artur Bartoszewski - Języki C i C++, sem.

Podstawy programowania. Wykład Pętle. Tablice. Krzysztof Banaś Podstawy programowania 1

Wprowadzenie do analizy składniowej. Bartosz Bogacki.

Podstawowe elementy proceduralne w C++ Program i wyjście. Zmienne i arytmetyka. Wskaźniki i tablice. Testy i pętle. Funkcje.

Analiza leksykalna 1. Języki formalne i automaty. Dr inż. Janusz Majewski Katedra Informatyki

Metody Kompilacji Wykład 13

Ćwiczenie 3. Konwersja liczb binarnych

1. Wartość, jaką odczytuje się z obszaru przydzielonego obiektowi to: a) I - wartość b) definicja obiektu c) typ oboektu d) p - wartość

JĘZYKI PROGRAMOWANIA Z PROGRAMOWANIEM OBIEKTOWYM. Wykład 6

Podstawy programowania w języku C

Podstawy programowania. 1. Operacje arytmetyczne Operacja arytmetyczna jest opisywana za pomocą znaku operacji i jednego lub dwóch wyrażeń.

Elżbieta Kula - wprowadzenie do Turbo Pascala i algorytmiki

Informatyka I. Typy danych. Operacje arytmetyczne. Konwersje typów. Zmienne. Wczytywanie danych z klawiatury. dr hab. inż. Andrzej Czerepicki

Operacje wykonywane są na operandach (argumentach operatorów). Przy operacji dodawania: argumentami operatora dodawania + są dwa operandy 2 i 5.

Programowanie obiektowe

JAO - Wprowadzenie do Gramatyk bezkontekstowych

Programowanie obiektowe

Programowanie w C++ Wykład 2. Katarzyna Grzelak. 5 marca K.Grzelak (Wykład 1) Programowanie w C++ 1 / 41

Uwagi dotyczące notacji kodu! Moduły. Struktura modułu. Procedury. Opcje modułu (niektóre)

Algorytm. a programowanie -

Szablony funkcji i szablony klas

PARADYGMATY PROGRAMOWANIA Wykład 4

/* dołączenie pliku nagłówkowego zawierającego deklaracje symboli dla wykorzystywanego mikrokontrolera */ #include <aduc834.h>

C++ - klasy. C++ - klasy. C++ - klasy. C++ - klasy. C++ - klasy WSKAŹNIKI KLASOWE

zmienne stanowią abstrakcję komórek pamięci: programista może przechowywać dane w pamięci, nie martwiąc się o techniczne szczegóły (np.

1 Podstawy c++ w pigułce.

Gramatyka operatorowa

Wstęp do informatyki. Maszyna RAM. Schemat logiczny komputera. Maszyna RAM. RAM: szczegóły. Realizacja algorytmu przez komputer

Zadanie Zaobserwuj zachowanie procesora i stosu podczas wykonywania następujących programów

Spis treści WSTĘP CZĘŚĆ I. PASCAL WPROWADZENIE DO PROGRAMOWANIA STRUKTURALNEGO. Rozdział 1. Wybór i instalacja kompilatora języka Pascal

ISO/ANSI C - funkcje. Funkcje. ISO/ANSI C - funkcje. ISO/ANSI C - funkcje. ISO/ANSI C - funkcje. ISO/ANSI C - funkcje

Programowanie. programowania. Klasa 3 Lekcja 9 PASCAL & C++

Transkrypt:

Co to jest kompilator? Co robi kompilator? Analiza Program który tłumaczy programy w języku wyższego poziomu na kod maszynowy procesora (np 80x86, Sparc) lub maszyny wirtualnej (np. JVM). Różnice miedzy interpreterem a kompilatorem: interpreter wykonuje program, kompilator nie wykonuje programu, a tylko tłumaczy go; stworzenie interpretera nie wymaga znajomości maszyny docelowej, stworzenie kompilatora wymaga dogłębnej znajomości maszyny docelowej. Wczytuje program, zwykle jako tekst. Sprawdza poprawność i dokonuje analizy programu. Myśli chwilę (dokonuje szeregu transformacji programu). Generuje kod wynikowy (synteza). Części kompilatora realizujace analizę i syntezę określa się czasem nazwami front-end i back-end Istnieje wiele podobnych klas problemów/programów, gdzie analizujemy dane wejściowe (zwykle tekst) tłumaczymy na inny język. 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 użycia identyfikatorów z odpowiednimi deklaracjami; kontrola typów. Każda z faz analizy powinna dawać czytelne komunikaty o napotkanych błędach Trudne, ale bardzo ważne. Faza analizy jest niezależna od języka docelowego. Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 1 października 2012 2 / 36 Analiza semantyczna Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 1 października 2012 3 / 36 Maszyny docelowe Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 1 października 2012 4 / 36 Synteza Analiza deklaracji Zapis informacji w tablicy symboli Kontrola poprawności użycia symboli i powiazanie z odpowiednimi deklaracjami (poprzez tablicę symboli). Kontrola (lub rekonstrukcja) typów. Fizyczna architektura procesora, np x86, x86_64, SPARC, ARM Maszyna wirtualna stosowa, np. JVM rejestrowa, np. LLVM Maszyna wirtualna może być użyta jako etap pośredni na drodze do kodu maszynowego Ahead of Time (AOT) generacja kodu maszynowego przed rozpoczęciem wykonania programu (np. LLVM); Just in Time (JIT) generacja kodu w trakcie wykonania, dla wybranych fragmentów programu (np. JVM). Transformacja drzewa struktury do postaci dogodnej do dalszych przekształceń (kod pośredni) 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ęzyków Programowania 1 października 2012 9 / 36 Generatory analizatorów leksykalnych Pisanie analizatora leksykalnego jest zwykle żmudne. Dlatego przeważnie jest on generowany automatycznie przez narzędzia takie jak Flex (C,C++), JLex (Java), Alex (Haskell), Ocamllex, C#Lex,... Narzędzia takie generuja program realizujacy automat rozpoznajacy leksemy na podstawie opisu złożonego z wyrażeń regularnych i przypisanych im akcji. Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 1 października 2012 10 / 36 Analiza syntaktyczna Analizator syntaktyczny (parser) jest funkcja sprawdzajac a, czy dane słowo należy do języka i, jeśli tak, budujac a drzewo struktury. Algorytm Youngera: O(n 3 ) i nie daje drzewa struktury. Istnieja efektywne algorytmy dla pewnych klas gramatyk. Dwa zasadnicze podejścia: Top-down: próbujemy sparsować określona konstrukcję (nieterminal); drzewo struktury budowane od korzenia do liści. Bottom-up: W danym napisie znajdujemy możliwe konstrukcje; drzewo budowane od liści do korzenia ze znalezionych kawałków. Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 1 października 2012 11 / 36 Analiza top-down Schemat analizy top-down możem zapisać jako automat: Jeden stan, alfabet stosowy Γ = N T, akceptacja pustym stosem. Na szczycie stosu a T : jeśli na wejściu a zdejmij ze stosu, wczytaj następny symbol. wpp bład: oczekiwano a. Na szczycie stosu A N, na wejściu a: wybieramy produkcję A α taka, że a SELECT(A α) na stosie zastępujemy A przez α Powyzszy automat oglada jeden symbol z wejścia, ale łatwo go uogólnić na większa ich liczbę automat LL(k). Dla automatu deterministycznego, wybór produkcji jest ważny; zbiór symboli dla których wybieramy produkcję A α nazywamy SELECT(A α). Teraz zajmiemy się obliczaniem tego zbioru. Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 1 października 2012 22 / 36 Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 1 października 2012 23 / 36 Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 1 października 2012 24 / 36

Zbiory FIRST Notacja Niech w T k : w = (pierwszych k znaków słowa w) Definicja (FIRST ) Niech w (T N). { a 1 a 2...a k, jeśli w = a 1 a 2...a k v w#, jeśli w < k. FIRST k (w) = {α : β T, w β, α = k : β (pierwsze k znaków słów wyprowadzalnych z w). FIRST(w) = FIRST 1 (w) Zbiory FOLLOW Definicja (FOLLOW) Niech w N FOLLOW k (w) = {α : β T, S µwβ, α = k : β (pierwsze k znaków mogacych wystapić za w). Gramatyki LL(k) Czytajac od Lewej, Lewostronny wywód, widzimy (k) symboli. Definicja Gramatyka jest LL(k), jeśli dla każdych lewostronnych wyprowadzeń S waα wβα wx S waα wγα wy takich, że FIRST k (x) = FIRST k (y), mamy β = γ Jeżeli pierwszych k symboli wyprowadzalnych z A jest wyznaczone jednoznacznie, to także jednoznaczne jest, która produkcja dla A musi być zastosowana w wyprowadzeniu lewostronnym. Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 1 października 2012 25 / 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), jeśli dla każdej pary (różnych) produkcji A α, A β ich zbiory SELECT k sa rozłaczne. Dla gramatyk silnie LL(k) łatwo zbudować parser top-down. Każda gramatyka silnie LL(k) jest też LL(k). Każda gramatyka LL(1) jest silnie LL(1). Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 1 października 2012 28 / 36 Wyliczanie FIRST Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 1 października 2012 26 / 36 Lewostronna faktoryzacja Problem pierwszego rodzaju możemy rozwiazać wyłaczaj ac przed nawias wspólne poczatki produkcji: A αβ αγ zastępujemy przez A αz Z β γ gdzie Z jest świeżym nieterminalem. Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 1 października 2012 30 / 36 Wyliczanie FOLLOW Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 1 października 2012 27 / 36 Eliminacja lewostronnej rekursji Zbiór produkcji A Aα β zastępujemy Na przykład, dla gramatyki otrzymujemy gramatykę lub, prościej A βr R αr ε E E + T T E TR R +TR ε E T + E T Niestety, teraz + łaczy w prawo, a nie jak chcieliśmy w lewo. Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 1 października 2012 31 / 36 Dla t T mamy FIRST(t) = {t. Dla A N mamy: A α 1... α n FIRST(A) FIRST(α 1 )... FIRST(α n ) Dla A X 1...X n FIRST(A) FIRST(X 1 )\{# X 1 ε FIRST(A) FIRST(X 2 )\{# X 1 X 2 ε FIRST(A) FIRST(X 3 )\{#... X 1 X 2...X n ε # FIRST(A) Prosty algorytm: zgodnie z powyższymi regułami powiększamy zbiory FIRST tak długo, jak któryś ze zbiorów się powiększa (obliczamy najmniejszy punkt stały). Dla każdych 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 powyższymi regułami powiększamy zbiory FOLLOW tak długo, jak któryś ze zbiorów się powiększa (obliczamy najmniejszy punkt stały). Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 1 października 2012 32 / 36 Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 1 października 2012 34 / 36

Metoda zejść rekurencyjnych Wersja pragmatyczna Przykład Metoda tworzenia parsera top-down jako zbioru wzajemnie rekurencyjnych funkcji: 1 przekształcamy gramatykę do postaci LL(1) 2 liczymy zbiory SELECT 3 (wersja ortodoksyjna) dla każdego nieterminala A piszemy osobna, rekurencyjna funkcję A. Funkcja A rozpoznaje najdłuższy ciag terminali (leksemów) wyprowadzalny z A. Zakładajac, że mamy już gramatykę LL(1) i policzone zbiory SELECT: 1 dla każdego nieterminala tworzymy graf składniowy; rozgałęzienie odpowiada wyborowi produkcji, zatem zbiory SELECT służa wyborowi drogi. 2 Sklejamy grafy, aby zmniejszyć ich liczbę, a przez to i liczbę wywołań funkcji. 3 Zastępujemy rekursję ogonowa przez iterację. 4 Dla każdego grafu piszemy funkcję; graf jest schematem blokowym takiej funkcji i wystarczy go starannie zakodować. Gramatyka: Grafy składniowe: E T R R + T E TR R R +TR ε Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 8 października 2012 10 / 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 Języków Programowania 8 października 2012 15 / 30 Jak kontynuować analizę 1 Znaleźć możliwie małe poddrzewo zawierajace bład. 2 Pominać leksemy aż do końca 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łąd(...); do {next(); while(!infollowf(lexem)); Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 8 października 2012 16 / 30 Budowa drzewa struktury a transformacje LL(1) Faktoryzacja gramatyki: daje w wyniku: E T + E T E TR R +E ε Jak zbudować drzewo dla R? Jakiego w ogóle typu ma być funkcja dla R? Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 8 października 2012 17 / 30 Kontynuacje na pomoc Możemy zauważyć, że R jest kontynuacja T. Argumentem dla R będzie węzeł 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 Języków Programowania 8 października 2012 19 / 30 Eliminacja lewostronnej rekursji staje się E E + T T E TR R +TR ε Czyli podobnie jak w poprzednim przypadku. Musimy tylko zadbać 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 Języków Programowania 8 października 2012 21 / 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 wiaż acych w prawo pozostawiamy w wersji rekurencyjnej (czyli tak jak w wersji ortodoksyjnej ). Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 8 października 2012 22 / 30 Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 8 października 2012 23 / 30 Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 8 października 2012 24 / 30

Jak rozpoznać uchwyt? Działanie automatu LR Konstrukcja automatu LR Zbudujemy automat skończony rozpoznajacy wiele wzorców (możliwe prawe strony produkcji) Sytuacja LR(0) A α β czyli produkcja z wyróżnionym miejscem. Jesteśmy w trakcie rozpoznawania A αβ, na stosie jest już α, trzeba jeszcze rozpoznać β Sytuacja A α oznacza, ze na stosie mamy cała prawa stronę produkcji i możemy redukować (w metodzie SLR(1) tylko gdy na wejściu mamy a FOLLOW(A)). 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 wejściu terminal a: ACTION[s, a] = shift p przenosi a z wejścia na stos i nakrywa stanem p ACTION[s, a] = reduce(a α) zdejmuje α par ze stosu odsłoni się stan q (zawierał sytuację... A...) wkłada na stos A, GOTO[q, A]. Specjalne akcje: error, accept 1 Rozszerzamy gramatykę o produkcję Z S# (nowy symbol poczatkowy) 2 Budujemy automat skończony: stanami sa zbiory sytuacji stan poczatkowy: Closure({Z S#) dla stanu p przejście po symbolu X do stanu δ(p, X) = Closure({A αx γ : A α Xγ p) stanem akceptujacym jest {Z S# 3 Wypełniamy tablicę sterujac a automatu ze stosem. Przykład na tablicy Marcin Benke (MIM UW) MRJP 4/ 22 18 października 2012 4 / 22 Wypełnianie tablic sterujacych Marcin Benke (MIM UW) MRJP 5/ 22 18 października 2012 5 / 22 Redukcje Marcin Benke (MIM UW) MRJP 6/ 22 18 października 2012 6 / 22 Sytuacje LR(1) Numerujemy stany, numerujemy produkcje. Jednolicie dla wszystkich klas automatów wpisujemy akcje shift (przepisujemy przejścia automatu skończonego) i accept: X p q Dla przejścia wpisujemy: jeśli X jest terminalem to jeśli X jest nieterminalem to ACTION[p, x] = shift q GOTO[p, x] = q Jeśli stan p zawiera S S #, to ACTION[p,#] = accept Tu postępujemy różnie dla różnych klas automatów. Jeśli 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. Jeśli gdzieś zostanie wpisana więcej niż jedna akcja, to źle: gramatyka nie jest odpowiedniej klasy (konflikt shift-reduce lub reduce-reduce). Przykład na tablicy Sytuacja LR(1) [A α β, a] czyli para zawierajaca sytuację LR(0) i terminal. Jesteśmy w trakcie rozpoznawania A αβ, na stosie jest już α, trzeba jeszcze rozpoznać β. Ponadto istnieje wyprowadzenie prawostronne postaci S µaaw µαβaw... µα takie, że µα prowadzi do bieżacego stanu (q 0 q). Sytuacja [A α, a] oznacza, ze na stosie mamy cała prawa stronę produkcji; możemy redukować gdy na wejściu jest a. Marcin Benke (MIM UW) MRJP 7/ 22 18 października 2012 7 / 22 Stany i przejścia automatu LR(1) Marcin Benke (MIM UW) MRJP 8/ 22 18 października 2012 8 / 22 Konstrukcja automatu LALR(1) Marcin Benke (MIM UW) MRJP 11/ 22 18 października 2012 11 / 22 Zależności między klasami gramatyk Stanami automatu sa zbiory sytuacji LR(1). Jeśli jesteśmy w sytuacji [B α Aβ, a], to w wyprowadzeniu po A może wystapić symbol z FIRST(βa). Jesteśmy zatem też w sytuacji [A γ, b] dla każdego A γ P oraz b FIRST(βa). Stan musi być domknięty zwn tę implikację: Closure(Q) najmniejszy zbiór zawierajacy Q oraz taki, że jeśli [B α Aβ, a] Closure(Q),to A γ P, b FIRST(βa) [A γ, b] Closure(Q) Jeśli [A α Xγ, a] Q dla pewnego X N T, to ze stanu Q jest przejście (po X) do stanu Closure({[A αx γ, a]). Budujemy automat ze zbiorów sytuacji LR(1). Sklejamy równoważne stany (sumujemy stany majace identyczne jadra). Dalej postępujemy jak w metodzie LR(1). Jeśli nie powstana nowe konflikty, to gramatyka jest LALR(1). Zauważmy, że: Względem LR(1) moga powstać 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 Pomiędzy klasami gramatyk zachodza inkluzje LR(0) SLR(1) LALR(1) LR(1) Wszystkie powyższe inkluzje sa ostre. Ponadto LL(1) LR(1) Uwaga: istnieja gramatyki LL(1), które nie sa LALR(1), czyli LL(1) LALR(1) Marcin Benke (MIM UW) MRJP 14/ 22 18 października 2012 14 / 22 Marcin Benke (MIM UW) MRJP 20/ 22 18 października 2012 20 / 22 Marcin Benke (MIM UW) MRJP 22/ 22 18 października 2012 22 / 22

Analiza semantyczna Analiza nazw Czy x jest zadeklarowane przed użyciem? Która deklaracja x obowiazuje w danym miejscu programu? Czy jakieś nazwy sa zadeklarowane a nie używane? Analiza zgodności typów Czy wyrażenie e jest poprawne typowo? Jakiego typu jest e? Czy funkcja zawsze zwraca wartość typu zgodnego z zadeklarowanym? Identyfikacja operacji Jaka operację reprezentuje + wyrażeniu a+b? Odpowiedzi na te pytania moga wymagać informacji nielokalnych kontekstowych. Nie sa to własności bezkontekstowe. Gramatyki atrybutywne Wygodnym narzędziem 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 : X 0 X 1...X n definiujemy reguły atrybutowania R(p) = {X i.a f i,a (X j.b...x k.c) 0 i n, a A(X i ) Well defined Attribute Grammar Majac drzewo struktury chcemy dla każdego wierzchołka X wyznaczyć wartości wszystkich atrubutów zgodnie z regułami atrybutowania. Definicja (WAG) Gramatyka atrybutywna jest dobrze zdefiniowana jeśli dla każdego drzewa struktury zgodnego z ta gramatyka można w sposób jednoznaczny wyznaczyć wartości wszystkich atrybutów. Nieważne jak, ważne, że można. Zagrożenia: brak reguły, sprzeczne reguły, cykl Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 25 października 2011 2 / 37 Atrybuty syntetyzowane i dziedziczone Dla każdej produkcji p : X 0 X 1...X n zbiorem definiujacych wystapień atrybutów jest AF(p) = {X i.a X i.a f( ) R(p) Atrybut X.a jest syntetyzowany, jeśli istnieje produkcja p : X α i X.a AF(p) (czyli zależy od poddrzewa) Atrybut X.a jest dziedziczony, jeśli istnieje produkcja q : Y αxβ i X.a AF(q) (czyli zależy 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 Języków Programowania 25 października 2011 3 / 37 Przykład atrybut syntetyzowany Konwencja: jeśli dany symbol występuje więcej niż raz w danej produkcji, jego wystapienia numerujemy. Atrybuty: E.val, T.val, F.val syntetyzowane, num.val wbudowany E 0 E 1 + T {E 0.val E 1.val + T.val E T {E.val T.val T T F{T 0.val T 1.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 Języków Programowania 25 października 2011 4 / 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 L 0 L 1, id {L 1.typ L 0.typ, id.typ L 0.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 Języków Programowania 25 października 2011 5 / 37 Gramatyki zupełne Gramatyka jest zupełna, jeśli dla każdego symbolu X spełnione sa warunki: 1 dla każdej produkcji p : X α mamy AS(X) AF(p), 2 dla każdej produkcji q : Y αxβ mamy AI(X) AF(q), 3 AS(X) AI(X) = A(X), 4 AS(X) AI(X) =. Możliwa implementacja: wierzchołki drzewa struktury obiekty odp. klas atrybuty syntetyzowane metody wirtualne atrubuty dziedziczone przekazywane jako argumenty tychże metod, Atrybuty można przechowywać także jako atrybuty wierzchołków, by uniknać wielokrotnego ich wyliczania. Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 25 października 2011 6 / 37 Łatwe klasy gramatyk dobrze zdefiniowanych Gramatyka S-atrybutowana: wszystkie atrybuty sa syntetyzowane wyliczanie atrybutów od liści do korzenia dobrze łaczy się z analiza wstępujac a Gramatyka L-atrybutowana: atrybuty moga być syntetyzowane badź dziedziczone atrybuty dziedziczone zależa tylko od rodzica i rodzeństwa na lewo można wyliczyć przechodzac drzewo struktury DFS Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 25 października 2011 7 / 37 Generalized LR Metoda oryginalnie wymyślona przez Tomitę dla analizy języków naturalnych. Budowa automatu LR dla gramatyki niejednoznacznej prowadzi do konfliktów (kilka możliwych akcji w jednej sytuacji). Każdy element tablicy automatu GLR moze zawierać zbiór akcji. Jeśli w danym momencie mamy zbiór akcji (> 1), automat rozmnaża się na odpowiednia liczbę kopii. Przy napotkaniu błędu kopia ginie Efekt: zbiór możliwych rozbiorów danego tekstu. Niektóre generatory (Bison,Happy) potrafia generować parsery GLR. Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 25 października 2011 8 / 37 Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 25 października 2011 10 / 37 Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 25 października 2011 26 / 37

Tablica symboli Zasięg i zakres Przykład Opis wszystkich bytów (zmienych, funkcji, typów, klas, atrybutów, metod,... ) wystepujacych w programie. Musi mieć narzucona strukturę (mechanizm wyszukiwania), odzwierciedlajac a reguły wiazania identyfikatorów w danym języku. Opis bytu: rodzaj definicji inne informacje zależne od rodzaju Byty moga być wzajemnie powiazane. Zasięg definicji identyfikatora to obszar programu, w którym możemy użyć identyfikatora w zdefiniowanym znaczeniu. Nie musi być ciagły. Zakres to konstrukcja składniowa, z która moga być zwiazane definicje identyfikatorów (funkcja, blok, itp.) void f() { int a; a = g(); { string a; b = a; h(a,b); Zasięg deklaracjiint a jest zaznaczony na czerwono. Jest ona zwiazana z zakresem funkcji f. Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 25 października 2011 27 / 37 Drzewo zagnieżdżeń Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 25 października 2011 29 / 37 Drzewo zagnieżdżeń Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 25 października 2011 30 / 37 Metoda I: stos tablic symboli Problem: analizujemy węzeł drzewa struktury, np przypisanied:=e+1. Gdzie sa definicjedie? Problem: analizujemy węzeł drzewa struktury, np przypisanied:=e+1. Gdzie sa definicjedie? Q acd R abd P abe M ad T acd S abc Wyszukiwanie: przeszukaj zakresy od bieżacego do znalezienia lub do końca, jeżeli nie znaleziono, to dodaj fikcyjna definicję dla uniknięcia kaskady błędów. Wejście do zakresu: połóż na stos nowa tablicę symboli, umieść w niej definicje zwiazane z tym zakresem Wyjście z zakresu: zdejmij ze stosu ostatnia tablicę symboli Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 25 października 2011 32 / 37 Metoda II: tablica stosów Dla każdego identyfikatora tworzymy osobny stos odwołań do jego definicji Niezmiennik: w trakcie analizy, dla każdego identyfikatora na szczycie stosu jest odsyłacz do aktualnej definicji (lub stos pusty). Wejście do zakresu: przechodzimy listę definicji zwiazanych z zakresem i wkładamy odsyłacze do nich na odpowiednie stosy. Wyjście z zakresu: przechodzimy ponownie listę definicji i zdejmujemy odsyłacze ze stosów. W porównaniu z Metoda I nieco więcej pracy na granicach zakresów, ale za to szybsze wyszukiwanie. Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 25 października 2011 34 / 37 Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 25 października 2011 32 / 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 wartość mac.c? Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 25 października 2011 35 / 37 Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 25 października 2011 33 / 37 Przykład A: int a global: type int class Object,A,B Object C: int c con C() B: int a con B() class C C C() Java odwiedza najpierw czerwona krawędź. Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 25 października 2011 37 / 37

Systemy typów System typów zbiór typów i reguł wnioskowania o poprawności typowej konstrukcji języka (głównie wyrażeń) Reguły sa zwykle wyrażane w postaci A 1... A n B oznaczajacej jeśli A 1 i... i A n to możemy wnioskować B. Używamy też notacji Γ e : τ znaczacej w środowisku Γ, wyrażenie e ma typ τ. Środowisko przypisuje zmiennym typy, tzn. jest zbiorem par (x : τ), gdzie x jest zmienna zaś τ typem. Prosty system typów Typy: τ ::= int bool Wyrażenia: e ::= n b e 1 + e 2 e 1 = e 2 if e 0 then e 1 else e 2 Reguły: n : int b : bool e 1 : int e 2 : int e 1 : int e 2 : int e 1 + e 2 : int e 1 = e 2 : bool e 0 : bool e 1 : τ e 2 : τ if e 0 then e 1 else e 2 : τ Zmienne Rozszerzmy nasz język o zmienne: e ::= x n b e 1 + e 2 e 1 = e 2 if e 0 then e 1 else e 2 Typ zmiennej zależy od kontekstu, rozszerzymy zatem nasze reguły typowania o informacje o kontekście (środowisko). Będziemy używać notacji Γ e : τ znaczacej w środowisku Γ, wyrażenie e ma typ τ. Środowisko przypisuje zmiennym typy, tzn. jest zbiorem par (x : τ), gdzie x jest zmienna zaś τ typem. Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 29 października 2012 2 / 38 Reguły typowania w kontekście Stałe maja z góry ustalone typy: Γ n : int Γ b : bool Typy zmiennych odczytujemy ze środowiska: Γ e 1 : int Γ e 2 : int Γ e 1 + e 2 : int Γ(x : τ) x : τ Γ e 1 : int Γ e 2 : int Γ e 1 = e 2 : bool Γ e 0 : bool Γ e 1 : τ Γ e 2 : τ Γ if e 0 then e 1 else e 2 : τ Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 29 października 2012 3 / 38 Kontrola typów w językach imperatywnych Rozważmy mały język imperatywny: e ::= x n b e 1 + e 2 e 1 = e 2 s ::= x := e while e do s s; s Wprowadzimy nowy osad dla programów Γ P s o znaczeniu w środowisku Γ, program s jest poprawny. Niektóre reguły będa używać zarówno jak P, np. czy Γ x : τ Γ e : τ Γ P x := e Γ e : bool Γ P p Γ P while e do p Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 29 października 2012 5 / 38 Deklaracje Możemy uznać deklarację jako rodzaj instrukcji oraz regułę Γ(x : τ) P p Γ P var x : τ; p inna mozliwościa jest wprowadzenie nowego typu osadu, D : Γ D (var x : τ) : Γ(x : τ) Γ D ds : Γ Γ P p Γ P ds; p Można też pozwolić instrukcjom na modyfikację środowiska. Deklaracje i instrukcje moga być wtedy swobodnie przeplatane: Γ P s : Γ Γ P p : Γ Γ P s; p : Γ Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 29 października 2012 6 / 38 Kontrola typów w językach funkcyjnych Typy: Wyrażenia: Reguły typowania τ ::= int bool τ 1 τ 2 E ::=x n b e 1 e 2 λ(x:τ).e e 1 + e 2 e 1 = e 2 if e 0 then e 1 else e 2 Γ(x : τ) e : ρ Γ λ(x : τ).e : τ ρ Γ e 1 : τ a τ r Γ e 2 : τ 2 τ a = τ 2 Γ e 1 e 2 : τ r Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 29 października 2012 7 / 38 Rekonstrukcja typów Jeśli typy nie sa znane, trzeba zrekonstruować 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 wziać dobre τ? Możemy uczynić τ niewiadoma. Proces typowania da nam typ wraz z układem równań Przy każdym użyciu reguły aplikacji Γ e 1 : τ 1 τ Γ e 2 : τ 2 τ 1 = τ 2 Γ e 1 e 2 : τ dodajemy do układu równanie τ 1 = τ 2. Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 29 października 2012 8 / 38 Przykłady rekonstrukcji typów Możemy podobnie jak w Haskellu traktować a+b jako aplikację (+) a b. x : τ x x : τ x x : τ x 1 : int {τ x : τ x x + 1 : int x = int λx.x + 1 : τ x int (λx.x + 1) 7 : int z niemal trywialnym układem równań {τ x = int. 7 : int Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 29 października 2012 9 / 38 Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 29 października 2012 11 / 38 Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 29 października 2012 12 / 38

Przykłady rekonstrukcji typów Podobnie mozemy uzyskać Z równaniami: (λf.λx.f(fx))(λy.y) 7 : τ f τ f = τ x τ f (1) τ f = τ f τ f (2) τ f (τ x τ f y τ y ) (τ x τ f (3) τ x τ f = int τ f (4) Rozwiazywanie równań: unifikacja Otrzymane układy możemy rozwiazywać niemal tak samo jak każde inne: przez upraszczanie. W naszym przykładzie możemy uprościć równanie (4) do τ x τ f = int τ f τ x = int i podstawić int za x w pozostałych, otrzymujac τ f = int τ f τ f = τ f τ f τ f (int τ f ) = (τ y τ y ) (int τ f ) τ x = int τ f = int τ f (5) τ f = τ f τ f (6) τ f (int τ f y τ y ) (int τ f (7) τ x = int (8) Dalej możemy połaczyć (5) z (6) otrzymujac co może być uproszczone do Po podstawieniu int za τ f, mamy int τ f = τ f τ f τ f = int. τ f = int int (9) τ f = int (10) τ f (int int) = (τ y τ y ) (int int) (11) τ x = int (12) Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 29 października 2012 13 / 38 Upraszczajac (11) i podstawiajac τ f mamy skad ostatecznie int int = τ y τ y τ f = int int (13) τ f = int (14) τ y = int (15) τ x = int (16) Opisany proces rozwiazywania równań nazywamy unifikacja. W przypadku sukcesu wynikiem jest podstawienie. Fakt: unifikacja może być zastosowana do rozwiazywania równań na termach nad dowolna sygnatura. Rozstrzygalna w czasie liniowym. Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 29 października 2012 16 / 38 Polimorfizm przykłady i smutna konstatacja Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 29 października 2012 14 / 38 Kiedy unifikacja zawodzi Unifikacja zawodzi, gdy napotka jedno z poniższych: Równanie postaci (k 1 i k 2 sa różnymi stałymi) Równanie postaci (k stała): Równanie postaci k 1 = k 2 k = t t x = t gdzie x zmienna a t zawiera x ale różny 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 Języków Programowania 29 października 2012 17 / 38 Płytki polimorfizm Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 29 października 2012 15 / 38 Polimorfizm Z drugiej strony, układ równań może mieć więcej niż jedno rozwiazanie. W efekcie możemy wyprowadzić więcej niż jeden typ dla danego wyrażenia. Na przykład, mamy λx.x : τ τ dla każdego typu τ! Dla opisu tego zjawiska możemy wprowadzić nowa postać typu: α.τ, gdzie α jest zmienna typowa, oraz dwie nowe reguły: Γ e : τ Γ e : α.τ α FV(Γ) (τ[ρ/α] oznacza typ τ z ρ podstawionym za α). Γ e : α.τ Γ e : τ[ρ/α] Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 29 października 2012 18 / 38 Przeciażanie Możemy wyprowadzić Także λx.xx staje się typowalne: λx.x : α.α α λx.xx : β( α.α α) β β Niestety nowy system nie jest już sterowany składnia: nowe reguły nie odpowiadaja żadnym konstrukcjom składniowym i nie wiemy kiedy je stosować. Okazuje się, że rekonstrukcja typów w tym systemie jest nierozstrzygalna. Rekonstrukcja typów jest rozstrzygalna jeśli wprowadzimy pewne ograniczenie: kwantyfikatory sa dopuszczalne tylko na najwyższym poziomie oraz mamy specjalna składnię dla wiazań polimorficznych: Γ e 1 : τ 1 Γ(x : α.τ 1 ) e : τ Γ let x = e 1 in e : τ Taki system jest często wystarczajacy w praktyce. Na przykład możemy zastapić konstrukcję if funkcja if_then_else_ : α.bool α α α Jest on również podstawa systemów dla ML i Haskella (choć ten ostatni jest znacznie bardziej skomplikowany). Funkcje polimorficzne działaja niezależnie od typu argumentów. Przeciażanie oznacza, że jeden symbol funkcyjny (operator) oznacza różne funkcje dla różnych typów argumentów. Podczas kontroli typów przeciażone symbole sa zastępowane przez ich warianty odpowiednie dla typów argumentów. W systemie typów możemy to wyrazić następujaco: Γ e e : τ co oznacza w środowisku Γ, wyrażenie e ma typ τ i jest przekształcane do e. Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 29 października 2012 19 / 38 Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 29 października 2012 20 / 38 Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 29 października 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łuży też do obliczeń. Obiekty (w tym tablice) przechowywane na stercie. Stałe zmiennoprzecinkowe i napisowe przechowywane w obszarze stałych nie musimy się tym przejmować jeśli używamy Jasmina. Stos JVM Stos jest ciagiem ramek. Każda instancja metody ma swoja ramkę. Różne postaci wywołania: invokestatic dla metod statycznych (np. dla funkcji Latte) invokevirtual dla metod obiektowych invokespecial np. dla konstruktorów (dawniej invokenonvirtual) JVM zajmuje się kwestiami porzadkowymi, jak: alokacja i zwalnianie ramek przekazywanie parametrów dostarczanie wyników Struktura ramki stosu Ramka zawiera zmienne lokalne (w tym parametry) i stos operandów (dla obliczeń). Rozmiary tych obszarów musza być 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 jestthis, dla statycznych pierwszy argument. Stos operandów Element mieści wartość dowolnego typu. a Przed wywołaniem kładziemy argumenty na stosie, po powrocie wynik tamże. a double zajmuje dwie komórki, ale nie trzeba się tym przejmować Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 29 października 2012 22 / 38 Instrukcje JVM Maszyna stosowa load n załaduj n-ta zmienna lokalna (także parametry) store n zapisz wartość ze stosu do zmiennej lokalnej push val wstaw stała na stos add, sub, mul,... ldc stała getfield vname cname getstatic vname cname putfield vname cname operacje arytmetyczne załaduj stała z tablicy stałych pobierz pole z obiektu pobierz pole z klasy ustaw pole obiektu Instrukcje takie jakload, store,add sa prefiksowane typami, zatem np. aload, istore, fadd,... Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 29 października 2012 28 / 38 JVM obiekty/atrybuty Dyrektywa.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 Języków Programowania 29 października 2012 36 / 38 Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 29 października 2012 23 / 38 JVM tablice Instrukcje newarray typ utwórz tablicę (rozmiar na stosie) iaload załaduj element tablicyint (tablica i indeks na stosie) aastore zapisz do tablicy referencji (tablica, indeks, wartość 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 Języków Programowania 29 października 2012 34 / 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 Języków Programowania 29 października 2012 37 / 38 Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 29 października 2012 24 / 38 JVM tablice Kod 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 Języków Programowania 29 października 2012 35 / 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 Języków Programowania 29 października 2012 38 / 38

LLVM Low Level Virtual Machine,http://llvm.org/ maszyna rejestrowa, nieograniczona ilość rejestrów generacja kodu na rzeczywisty procesor przez alokację rejestrów (kolejny wykład) biblioteka C++, ale także format tekstowy kod trójadresowy (dwa źródła, jeden wynik): %t2 = add i32 %t0, %t1 jeden z argumentów może być 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 każdego wyniku (Static Single Assignment) Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 15 listopada 2012 2 / 34 LLVM typy (nie wszystkie) n-bitowe liczby całkowite: in, np.: i32 dlaint i1 dlabool i8 dlachar nie ma podziału na liczby ze znakiem i bez; sa operacje ze znakiem, np sle signed less or equal udiv unsigned div float orazdouble label void wskaźniki: t (np i8* oznacza char*) tablice: [n t] (uwaga: inny typ niż wskaźniki), np. hw = constant [13 x i8] c"hello world\0a\00" struktury{t 1,...,t n 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 się od, lokalne od% nazwy zewnętrzne sa deklarowane (printint) Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 15 listopada 2012 3 / 34 LLVM napisy Typchar* z C toi8* 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 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 Języków Programowania 15 listopada 2012 4 / 34 LLVM hello Literały napisowe sa tablicami[n x i8] Trzeba je rzutować doi8* np. przezbitcast 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 wskaźniki do swojej zawartości, dlatego użyciehellostr ma typ[n x i8]* Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 15 listopada 2012 5 / 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 Języków Programowania 15 listopada 2012 6 / 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 Języków Programowania 15 listopada 2012 7 / 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 końcu, bez możliwości zatrzymania ani rozgałęzienia wewnatrz. Kod LLVM: etykietowane bloki proste każdy blok kończy się skokiem (ret lubbr) nie ma automatycznego przejścia od ostatniej instrukcji bloku do pierwszej kolejnego (kolejność bloków może być swobodnie zmieniana) skoki warunkowe maja dwa cele: br i1 %c0, label %L0, label %L1 blok wejścia do funkcji jest specjalny: nie można do niego skoczyć Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 15 listopada 2012 9 / 34 Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 15 listopada 2012 10 / 34 Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 15 listopada 2012 11 / 34

Rejestry 8 32-bitowych rejestrów: EAX,EDX, EBX,ECX, ESI, EDI, ESP (wskaźnik stosu), EBP (wskaźnik ramki) Flagi Rejestr EFLAGS składa się z pól bitowych zwanych flagami, ustawianych przez niektóre instrukcje i używanych 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 można używać rejestrów 32-bitowych np. EAX, R8D oznaczaja połówki odpowiednio RAX, R8. Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 15 listopada 2012 17 / 34 Tryby adresowania pamięci W ogólności adres może być postaci baza + mnożnik indeks + przesunięcie gdzie baza i indeks sa rejestrami, na przykład EAX+4*EDI+7 Dodatkowe ograniczenia: ESP nie może być indeksem (pozostałe 7 rejestrów może) dopuszczalne mnożniki: 1,2,4,8 Składnia adresów Intel: [baza+mnożnik*indeks+przesunięcie] AT&T:przesunięcie(baza,indeks,mnożnik) Najczęściej używamy trybu baza+przesunięcie, np. mov 8(%ebp), %eax Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 15 listopada 2012 20 / 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 zależności od wyniku. W przypadku przepełnienia ustawiana jest flaga OF Przykład dodaj zawartość rejestru ESI do komórki pod adresem EBP+6: Intel: ADD [EBP+6], ESI AT&T:add %esi, 6(%ebp) Operandy (czyli argumenty instrukcji) Instrukcja składa się z tzw. mnemonika (kodu operacji) oraz 0 2 operandów (argumentów), którymi moga być: rejestr (r32/r64) stała (immediate operand, i8/i16/i32/i64), pamięć (m8/m16/m32/m64) Najwyżej jeden z operandów może odwoływać się do pamięci AT&T: rejestry prefiksowane %, stałe prefiksowane znakiem $ Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 15 listopada 2012 18 / 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 może przesłać między dwoma komórkami pamięci. Zamiana XCHG x, y zamienia zawartość swoich argumentów Instrukcje przesyłania nie zmieniaja flag. Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 15 listopada 2012 21 / 34 Mnożenie mnożenie przez 2 n można wykonac przy pomocy przesunięcia o n bitów w lewo (instrukcja SAL), np mnożenie przez 16 Intel: SAL EAX, 4 AT&T:sal $4, %eax mnożenie ze znakiem: IMUL;mnożna (i iloczyn) musi być w rejestrze, mnożnik w rejestrze lub pamięci Przykład pomnóż ECX przez zawartość komórki pod adresem ESP Intel: IMUL ECX, [ESP] AT&T:imul (%esp), %ecx Specjalna forma z jednym argumentem (mnożnikiem): IMUL r/m32 mnożna w EAX, wynik w EDX:EAX SAL ustawia flagi, IMUL tylko OF, CF. Rozmiary operandów x86 może operować na wartościach 8, 16, 32 lub 64-bitowych. Przeważnie z kontekstu wynika jaki rozmiar mamy na myśli, czasem jednak trzeba to explicite wskazać. W składni Intela wskazujemy to poprzedzajac operand prefiksem byte, word, dword lubqword, 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 niezbędne. Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 15 listopada 2012 19 / 34 Operacje na stosie PUSH src np. PUSH [EBP+4] PUSH DWORD 0 push %ebp pushl 0 POP dest np. pop 4(%ebp) POP [EBP+4] PUSHA/POPA połóż/odtwórz wszystkie 8 rejestrów. Uwaga: operacje na stosie używaja i automatycznie modyfikuja ESP, stos rośnie w dół PUSH zmniejsza ESP, ESP wskazuje na ostatni zajęty element stosu. Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 15 listopada 2012 22 / 34 Dzielenie dzielenie przez 2 n można wykonac przy pomocy przesunięcia 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 pamięci, iloraz w EAX, reszta w EDX NB: przy dzieleniu dwóch liczb 32-bitowych przed IDIV należy dzielna załadować do EAX, a jej znak do EDX, czyli jeśli dzielna dodatnia to EDX ma zawierać 0, jeśli ujemna to -1. Mozna ten efekt uzyskać przez przesunięcie o 31 bitów w prawo (albo używajac 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 mnożnika m]. Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 15 listopada 2012 23 / 34 Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 15 listopada 2012 24 / 34 Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 15 listopada 2012 25 / 34

Dzielenie Przykł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 użyciem CDQ: movl cdq idivl 28(%esp), %eax 24(%esp) Instrukcje porównania CMP x, y ustawia flagi w zależności od różnicy argumentów ZF jeśli różnica jest 0 SF jeśli różnica jest ujemna OF jeśli różnica przekracza zakres CF jeśli odejmowanie wymagało pożyczki Skoki Skok bezwarunkowy: JMP etykieta Skoki warunkowe w zależności od stanu flag; kody jak wynik CMP Porównania liczb bez znaku: Porównania liczb ze znakiem: Mnemoniki CMP skok gdy... JE/JZ = ZF = 1 JNE/JNZ ZF = 0 JAE/JNB CF = 0 JB/JNAE < CF = 1 JA/JNBE > (CF or ZF) = 0 JBE/JNA (CF or ZF) = 1 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 Języków Programowania 15 listopada 2012 26 / 34 Porównania przykład int cmp(int a, int b) { if(a>b) return 7; może zostać skompilowane do cmp: pushl %ebp movl %esp, %ebp movl 8(%ebp), %eax cmpl 12(%ebp), %eax # cmp a, b jng L4 # skocz jeśli warunek NIE zachodzi movl $7, %eax movl %eax, %edx movl %edx, %eax L4: popl %ebp ret Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 15 listopada 2012 29 / 34 Protokół wywołania funkcji x86_64 Liczby całkowite przekazywane w EDI,ESI,EDX,ECX,R8D,...,R15D wskaźniki 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 Języków Programowania 15 listopada 2012 27 / 34 Protokół wywołania funkcji CALL adres skok, śladem powrotu na stosie RET skok pod adres na szczycie stosu (zdejmuje go) Protokół używany przezgcc orazlibc przy wywołaniu na stosie argumenty od końca, ślad powrotu przy powrocie wynik typu int lub wskaźnik w EAX Standardowy prolog: pushl %ebp movl %esp, %ebp subl %esp, $x # zmienne lokalne Standardowy epilog: movl popl ret %ebp, %esp ; pomijane jesli nic nie zmieni %ebp Więcej o protokołach wywołania funkcji na kolejnych wykładach. Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 15 listopada 2012 30 / 34 Sztuczki Alternatywny epilog: leave ret instrukcja LEAVE przywraca ESP i EBP (istnieje też ENTER, ale jest za wolna) TEST x, y - wykonuje bitowy AND argumentów, ustawia SF i ZF zależnie od wyniku, zeruje OF i CF Najczęstsze użycie: ustalenie czy zawartość rejestru EAX jest dodatnie/ujemne/zero Intel: TEST EAX,EAX AT&T:test %eax, %eax Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 15 listopada 2012 28 / 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 Języków Programowania 15 listopada 2012 31 / 34 Sztuczki LEA ładuje do rejestru wyliczony adres (który może być postaci może być postaci baza+mnożnik*indeks+przesunięcie). Może być 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 pośrednie CALL r/m32 wywołanie funkcji o adresie zawartym w rejestrze/komórce pamięci może być użyte do realizacji metod wirtualnych JMP r/m32 skok jak wyżej, moze być użyty do implementacji instrukcjiswitch. Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 15 listopada 2012 32 / 34 Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 15 listopada 2012 33 / 34 Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 15 listopada 2012 34 / 34

Kod pośredni? Kod czwórkowy Instrukcje Nie jest niezbędny zwłaszcza przy generacji kodu na maszynę stosowa. Przy generacji kodu na różne architektury wspólne transformacje niezależne od architektury. Ułatwia niektóre optymalizacje. Różne postaci tu zajmiemy się najbardziej popularna: kodem czwórkowym. Czwórka: argumenty a 1, a 2 operacja lokalizacja wyniku w w := a 1 a 2 Zwany również kodem trójadresowym, większość instrukcji zawiera bowiem trzy adresy: wyniku i dwu argumentów. Przypisanie postacix := y op z, gdzieop to jeden z operatorów+,-,*,/,and,or,xor. Przypisanie jednoargumentowe postacix := op y, gdzie op to jeden z-, not. Kopiowanie postacix := y. Skoki bezwarunkowe postacigoto L, gdzie L to adres w kodzie zazwyczaj reprezentowany przez etykietę; przed każda instrukcja może wystapić etykieta odnoszaca się do pierwszego adresu występujacego po niej. Skoki warunkowe postaciif x oprel y goto L, gdzie oprel to operator relacyjny (<, >, =, <=, >=,!=). Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 19 listopada 2011 2 / 38 Instrukcje c.d. Wywoływanie funkcji, obsługiwane przez dwa rozkazy: param x i t := call L, n, służace 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 rozkazreturn x, gdzie x to wartość zwracana. Przypisania indeksowane postacix := y[i] orazx[i] := y. Pierwszy z tych rozkazów powoduje umieszczenie pod adresemx zawartości pamięci spod adresuy + i, drugi - umieszczenie pod adresemx + i wartości spod adresuy. Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 19 listopada 2011 5 / 38 Instrukcja warunkowa if(warunek) instrukcja 1 else instrukcja 2 Można wygenerować kod następujacy: 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 Języków Programowania 19 listopada 2011 21 / 38 Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 19 listopada 2011 3 / 38 Kolejność obliczeń Rozważmy wyrażenie e 1 + e 2, przy czym obliczenie e 1 wymaga k zmiennych tymczasowych (rejestrów), zaś e 2 n zmiennych (załóżmy, że n > k). Jeśli możemy obliczać e 1 i e 2 w dowolnym porzadku, to które lepiej obliczyć najpierw, aby zużyć jak najmniej zmiennych tymczasowych? 1 najpierw łatwiejsze : k rejestrów dla obliczenia e 1, potem 1 przechowujacy jego wartość plus n dla obliczenia e 2 2 najpierw trudniejsze max(k, 1+n) = 1+n max(n, 1+k) = n Kolejność wyliczenia może zadecydować, czy uda nam się obliczyć wyrażenie tylko przy użyciu rejestrów (bez odsyłania wyników pośrednich do pamięci). Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 19 listopada 2011 10 / 38 Skrócone tłumaczenie wyrażeń logicznych Wyrażenia logiczne można tłumaczyć albo tak jak wyrażenia arytmetyczne, albo przy użyciu tzw. kodu skaczacego if not w1 goto Lfalse if not w2 goto Lfalse kod Ltrue lub goto Ltrue w1&&w2 w1 w2 if w1 goto Ltrue if w2 goto Ltrue kod Lfalse lub goto Lfalse Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 19 listopada 2011 22 / 38 Marcin Benke (MIM UW) Metody Realizacji Języków Programowania 19 listopada 2011 4 / 38 Pętla while while(warunek) instrukcja Można wygenerować kod następujacy: L1: kod warunku, wynik w t if not t goto L2 kod instrukcji goto L1 L2:... Można też trochę 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 Języków Programowania 19 listopada 2011 20 / 38 Przykład JVM if((a>0&&b>0) (a<0&&b<0)) return 9; else return 1; Dalej jest przestrzeń dla ulepszeń, 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 Języków Programowania 19 listopada 2011 26 / 38