Generowanie kodu (kompilator) - Code generation (compiler)

W obliczanie , generowanie kodu jest procesem, w którym kompilator jest generator kodu przekształca pewną pośrednią REPREZENTOWANIE z kodu źródłowego w formie (na przykład, kod maszynowy ), który może być łatwo wykonana przez maszynę.

Zaawansowane kompilatory zazwyczaj wykonują wiele przejść przez różne formy pośrednie. Ten wieloetapowy proces jest stosowany, ponieważ wiele algorytmów do optymalizacji kodu są łatwiejsze do zastosowania po jednym na raz, albo dlatego, że wejście do jednego optymalizacji opiera się na wypełnionym przetwarzania wykonywane przez inną optymalizacji. Ta organizacja ułatwia również tworzenie jednego kompilatora, który może być przeznaczony dla wielu architektur, ponieważ tylko ostatni etap generowania kodu ( backend ) musi zostać zmieniony z docelowego na docelowy. (Aby uzyskać więcej informacji na temat projektu kompilatora, zobacz Compiler .)

Dane wejściowe do generatora kodu zazwyczaj składają się z drzewa analizy lub abstrakcyjnego drzewa składni . Drzewo jest przekształcane w liniową sekwencję instrukcji, zwykle w języku pośrednim, takim jak trzyadresowy kod . Dalsze etapy kompilacji mogą, ale nie muszą być określane jako „generowanie kodu”, w zależności od tego, czy wiążą się ze znaczącą zmianą w reprezentacji programu. (Na przykład przebieg optymalizacji wizjera prawdopodobnie nie będzie nazywany „generowaniem kodu”, chociaż generator kodu może zawierać przebieg optymalizacji wizjera).

Główne zadania

Oprócz podstawowej konwersji z reprezentacji pośredniej na liniową sekwencję instrukcji maszynowych, typowy generator kodu próbuje w jakiś sposób zoptymalizować wygenerowany kod.

Zadania, które są zwykle częścią zaawansowanej fazy „generowania kodu” kompilatora, obejmują:

Wybór instrukcji jest zwykle wykonywany poprzez rekurencyjne przechodzenie postorder w abstrakcyjnym drzewie składni, dopasowując poszczególne konfiguracje drzewa do szablonów; na przykład drzewo W := ADD(X,MUL(Y,Z))może zostać przekształcone w liniową sekwencję instrukcji przez rekurencyjne generowanie sekwencji dla t1 := Xi t2 := MUL(Y,Z), a następnie wyemitowanie instrukcji ADD W, t1, t2.

W kompilatorze, który używa języka pośredniego, mogą występować dwa etapy wyboru instrukcji — jeden do konwersji drzewa analizy na kod pośredni, a drugi etap, znacznie później, do konwersji kodu pośredniego na instrukcje z zestawu instrukcji maszyny docelowej. Ta druga faza nie wymaga przechodzenia przez drzewo; można to zrobić liniowo i zazwyczaj wymaga prostego zastąpienia operacji w języku pośrednim odpowiadającymi im kodami operacji . Jeśli jednak kompilator jest faktycznie tłumaczem języka (na przykład takim, który konwertuje Javę na C++ ), wtedy druga faza generowania kodu może obejmować budowanie drzewa z liniowego kodu pośredniego.

Generowanie kodu uruchomieniowego

Gdy generowanie kodu odbywa się w czasie wykonywania , tak jak w przypadku kompilacji just -in-time (JIT), ważne jest, aby cały proces był wydajny pod względem miejsca i czasu. Na przykład, gdy wyrażenia regularne są interpretowane i używane do generowania kodu w czasie wykonywania, często generowana jest niedeterministyczna maszyna stanów skończonych zamiast deterministycznej, ponieważ zwykle ta pierwsza może zostać utworzona szybciej i zajmuje mniej miejsca w pamięci niż ta druga. Pomimo generalnie generowania mniej wydajnego kodu, generowanie kodu JIT może korzystać z informacji profilowania, które są dostępne tylko w czasie wykonywania.

Pojęcia pokrewne

Podstawowe zadanie polegające na przyjmowaniu danych wejściowych w jednym języku i wytwarzaniu wyników w nietrywialnie innym języku może być rozumiane w kategoriach podstawowych operacji transformacyjnych teorii języka formalnego . W związku z tym niektóre techniki, które zostały pierwotnie opracowane do użytku w kompilatorach, zaczęły być stosowane również w inny sposób. Na przykład YACC (Yet Another Compiler-Compiler ) pobiera dane wejściowe w postaci Backus-Naur i konwertuje je na parser w C . Chociaż pierwotnie został stworzony do automatycznego generowania parsera dla kompilatora, yacc jest również często używany do automatyzacji pisania kodu, który musi być modyfikowany za każdym razem, gdy zmieniane są specyfikacje.

Wiele zintegrowanych środowisk programistycznych (IDE) obsługuje pewną formę automatycznego generowania kodu źródłowego , często przy użyciu algorytmów wspólnych z generatorami kodu kompilatora, chociaż zwykle mniej skomplikowanych. (Patrz również: przekształcenie programu , transformacja danych ).

Odbicie

Ogólnie rzecz biorąc, analizator składni i semantyki próbuje pobrać strukturę programu z kodu źródłowego, podczas gdy generator kodu wykorzystuje te informacje strukturalne (np. typy danych ) do tworzenia kodu. Innymi słowy, pierwszy dodaje informacje, podczas gdy drugi traci część informacji. Jedną z konsekwencji utraty informacji jest to, że refleksja staje się trudna lub nawet niemożliwa. Aby przeciwdziałać temu problemowi, generatory kodu często osadzają informacje syntaktyczne i semantyczne oprócz kodu niezbędnego do wykonania.

Zobacz też

Bibliografia