przegląd rozszerzonego potoku kompilacji GCC, w tym wyspecjalizowanych programów, takich jak preprocesor, asembler i linker.
GCC jest trójstopniową architekturą typową dla kompilatorów wielojęzycznych i wielordzeniowych. Wszystkie drzewa programów są konwertowane na wspólną abstrakcyjną reprezentację na „środkowym końcu”, umożliwiając optymalizację kodu i generowanie kodu binarnego we wszystkich językach.,
zewnętrzny interfejs GCC jest zgodny z konwencjami Uniksowymi. Użytkownicy wywołują program sterowników specyficznych dla języka (gcc
dla C,g++
dla C++, itd.), który interpretuje argumenty poleceń, wywołuje rzeczywisty kompilator, uruchamia asembler na wyjściu, a następnie opcjonalnie uruchamia linker, aby wytworzyć kompletny plik wykonywalny.
każdy z kompilatorów języka jest osobnym programem, który odczytuje kod źródłowy i wypisuje kod maszynowy. Wszystkie mają wspólną strukturę wewnętrzną., Front end per-language przetwarza kod źródłowy w tym języku i tworzy abstrakcyjne drzewo składni (w skrócie” drzewo”).
są one, jeśli to konieczne, konwertowane na reprezentację wejściową środkowego końca, zwaną formą ogólną; środkowy koniec stopniowo przekształca program w jego ostateczną formę. Do kodu stosuje się optymalizacje kompilatora i techniki analizy statycznej kodu (takie jak FORTIFY_SOURCE, dyrektywa kompilatora, która próbuje wykryć pewne przepełnienia bufora)., Działają one na wielu reprezentacjach, głównie niezależnej od architektury reprezentacji GIMPLE i zależnej od architektury reprezentacji RTL. Wreszcie, kod maszynowy jest wytwarzany przy użyciu dopasowania wzorców specyficznych dla architektury, pierwotnie opartego na algorytmie Jacka Davidsona i Chrisa Frasera.
GCC został napisany głównie w języku C z wyjątkiem części ada front end. Dystrybucja zawiera standardowe biblioteki dla Ada, C++ i Java, których kod jest głównie napisany w tych językach., Na niektórych platformach dystrybucja zawiera również bibliotekę uruchomieniową niskiego poziomu libgcc, napisaną w kombinacji niezależnego od maszyny C i specyficznego dla procesora kodu maszynowego, zaprojektowanego głównie do obsługi operacji arytmetycznych, których procesor docelowy nie może wykonać bezpośrednio.
GCC wykorzystuje wiele standardowych narzędzi w swojej kompilacji, w tym Perl, Flex, Bison i inne popularne narzędzia. Ponadto obecnie wymaga obecności trzech dodatkowych bibliotek w celu zbudowania: GMP, MPC i MPFR.
w maju 2010 roku komitet sterujący GCC postanowił zezwolić na użycie kompilatora C++ do kompilacji GCC., Kompilator miał być napisany głównie w C plus podzbiór funkcji z C++. W szczególności postanowiono, że programiści GCC mogą korzystać z funkcji destruktorów i generycznych w C++.
w sierpniu 2012 roku komitet sterujący GCC ogłosił, że GCC używa obecnie C++ jako języka implementacyjnego. Oznacza to, że aby zbudować GCC ze źródeł, wymagany jest kompilator C++, który rozumie standard ISO / IEC C++03.,
Front endsEdit
Front end składa się z wstępnego przetwarzania, analizy leksykalnej, analizy składniowej (parsingu) i analizy semantycznej. Celem front endów kompilatora jest akceptowanie lub odrzucanie programów kandydujących zgodnie z gramatyką i semantyką języka, identyfikowanie błędów i obsługa poprawnych reprezentacji programów na późniejszych etapach kompilatora. Ten przykład pokazuje kroki lexera i parsera wykonywane dla prostego programu napisanego w języku C.,
każdy front end używa parsera do tworzenia abstrakcyjnego drzewa składni danego pliku źródłowego. Ze względu na abstrakcję drzewa składniowego pliki źródłowe dowolnego z różnych obsługiwanych języków mogą być przetwarzane przez ten sam back-end. GCC zaczęło używać parserów LALR generowanych przez Bison, ale stopniowo przestawiło się na ręcznie pisane parsery rekurencyjne dla C++ w 2004 roku oraz dla C i Objective – C w 2006 roku. Od 2021 roku wszystkie przednie końce używają ręcznie pisanych parserów rekurencyjnych.
do GCC 4.,0 reprezentacja drzewa programu nie była w pełni niezależna od docelowego procesora. Znaczenie drzewa było nieco inne dla różnych frontów językowych, a fronty mogły dostarczać własnych kodów drzewa. Zostało to uproszczone wraz z wprowadzeniem GENERIC i GIMPLE, dwóch nowych form drzew niezależnych od języka, które zostały wprowadzone wraz z pojawieniem się GCC 4.0. GENERIC jest bardziej złożony, oparty na GCC 3.x Java front end ' s intermediate representation. GIMPLE jest uproszczonym GENERYKIEM, w którym różne konstrukcje są obniżane do wielu instrukcji GIMPLE., Interfejsy C, C++ i Java generują GENERIC bezpośrednio w interfejsie. Inne przednie końce mają zamiast tego różne reprezentacje pośrednie po przetworzeniu i konwertują je na ogólne.
w obu przypadkach tak zwany „gimplifier” konwertuje tę bardziej złożoną formę na prostszą, opartą na SSA formę GIMPLE, która jest wspólnym językiem dla wielu potężnych, niezależnych od języka i architektury, globalnych optymalizacji (zakresu funkcji).,
GENERIC i GIMPLEEdit
GENERIC jest pośrednim językiem reprezentacji używanym jako „środkowy koniec” podczas kompilacji kodu źródłowego do binariów wykonywalnych. Podzbiór, zwany GIMPLE, jest skierowany przez wszystkie przednie końce GCC.
środkowy etap GCC wykonuje całą analizę i optymalizację kodu, pracując niezależnie zarówno od skompilowanego języka, jak i architektury docelowej, zaczynając od ogólnej reprezentacji i rozszerzając ją o register transfer language (RTL)., Reprezentacja ogólna zawiera tylko podzbiór imperatywnych konstrukcji programistycznych zoptymalizowanych przez środkowy koniec.
podczas przekształcania kodu źródłowego do GIMPLE złożone wyrażenia są dzielone na trzy-adresowy kod przy użyciu zmiennych tymczasowych. Reprezentacja ta została zainspirowana prostą reprezentacją zaproponowaną w kompilatorze McCAT przez Laurie J. Hendrena dla uproszczenia analizy i optymalizacji programów imperatywnych.,
OptimizationEdit
Optimization może wystąpić podczas każdej fazy kompilacji; jednak większość optymalizacji jest wykonywana po analizie składni i semantycznej front-endu, a przed wygenerowaniem kodu back-endu; dlatego powszechną, choć nieco sprzeczną, nazwą dla tej części kompilatora jest ” middle end.”
dokładny zestaw optymalizacji GCC różni się w zależności od wydania, ale zawiera standardowe algorytmy, takie jak optymalizacja pętli, wątek przeskoku, wspólna eliminacja podekspresji, planowanie instrukcji i tak dalej., Optymalizacje RTL mają mniejsze znaczenie z dodatkiem globalnych optymalizacji opartych na SSA na drzewach GIMPLE, ponieważ optymalizacje RTL mają znacznie bardziej ograniczony zakres i mają mniej informacji na wysokim poziomie.
niektóre z tych optymalizacji wykonywanych na tym poziomie obejmują eliminację martwego kodu, eliminację częściowej redundancji, globalną numerację wartości, nieliczne propagacje stałej warunkowej i skalarne zastępowanie agregatów. Wykonywane są również optymalizacje oparte na zależności między tablicami, takie jak automatyczna wektoryzacja i automatyczna równoległobok. Możliwa jest również optymalizacja pod kątem profilu.,
Back endEdit
zaplecze GCC jest częściowo określone przez makra preprocesora i funkcje specyficzne dla docelowej architektury, na przykład w celu zdefiniowania jego endianness, Rozmiar słowa i konwencje wywołania., Przednia część tylnego końca wykorzystuje je do decydowania o generowaniu RTL, więc chociaż RTL GCC jest nominalnie niezależny od procesora, początkowa Sekwencja abstrakcyjnych instrukcji jest już dostosowana do celu. W każdej chwili rzeczywiste instrukcje RTL tworzące reprezentację programu muszą być zgodne z opisem maszyny docelowej architektury.
plik opisu Maszyny zawiera wzorce RTL, wraz z ograniczeniami operandów i urywki kodu do wyjścia końcowego złożenia., Ograniczenia wskazują, że określony wzorzec RTL może mieć zastosowanie tylko (na przykład) do niektórych rejestrów sprzętowych lub (na przykład) zezwalać na natychmiastowe przesunięcia operandów o ograniczonym rozmiarze (np. 12, 16, 24, … bitowe offsety itp.). Podczas generowania RTL sprawdzane są ograniczenia dla danej architektury docelowej. W celu wydania danego fragmentu RTL, musi on pasować do jednego (lub więcej) wzorców RTL w pliku opisu Maszyny i spełniać ograniczenia dla tego wzorca; w przeciwnym razie nie byłoby możliwe przekonwertowanie końcowego RTL na kod maszynowy.,
pod koniec kompilacji poprawny RTL jest zredukowany do ścisłej formy, w której każda instrukcja odnosi się do rzeczywistych rejestrów maszynowych i wzorca z pliku opisu maszyny docelowej. Formowanie ścisłego RTL jest skomplikowanym zadaniem; ważnym krokiem jest alokacja rejestrów, gdzie rzeczywiste rejestry sprzętowe są wybierane w celu zastąpienia pierwotnie przypisanych pseudo-rejestrów. Po tym następuje faza „przeładowania”; wszelkie pseudo-rejestry, które nie zostały przypisane do prawdziwego rejestru sprzętowego, są „rozlewane” na stos i generowane jest RTL do tego rozlewania., Podobnie, przesunięcia, które są zbyt duże, aby zmieścić się w rzeczywistej instrukcji, muszą zostać podzielone i zastąpione sekwencjami RTL, które będą spełniać ograniczenia przesunięcia.
w końcowej fazie kod maszynowy jest budowany przez wywołanie małego fragmentu kodu, powiązanego z każdym wzorcem, w celu wygenerowania rzeczywistych instrukcji z zestawu instrukcji celu, za pomocą końcowych rejestrów, offsetów i adresów wybranych podczas fazy przeładowania. Fragment generujący assembly może być tylko ciągiem znaków, w którym to przypadku wykonuje się proste zastępowanie rejestrów, przesunięć i/lub adresów w łańcuchu znaków., Urywek generujący asembler może być również krótkim blokiem kodu C, wykonującym dodatkową pracę, ale ostatecznie zwracającym ciąg zawierający poprawny kod asemblera.
Inne funkcjeedit
niektóre funkcje GCC obejmują:
- Link-time optimization optymalizuje granice plików obiektowych, aby bezpośrednio ulepszyć połączony plik binarny. Optymalizacja czasu łączy opiera się na pliku pośrednim zawierającym serializację niektórych reprezentacji Gimple zawartych w pliku obiektowym. Plik jest generowany wraz z plikiem obiektowym podczas kompilacji źródła., Każda kompilacja źródłowa generuje oddzielny plik obiektowy i plik pomocniczy link-time. Gdy pliki obiektowe są połączone, kompilator jest wykonywany ponownie i używa plików pomocniczych do optymalizacji kodu między osobno skompilowanymi plikami obiektowymi.
- wtyczki mogą bezpośrednio rozszerzyć kompilator GCC. Wtyczki pozwalają kompilator zapasów być dostosowane do konkretnych potrzeb przez Zewnętrzny kod załadowany jako wtyczki. Na przykład wtyczki mogą dodawać, zastępować lub nawet usuwać przejścia środkowe działające na reprezentacjach Gimple., Kilka wtyczek GCC zostało już opublikowanych, w szczególności wtyczka GCC Python, która łączy się z libpythonem i pozwala wywoływać dowolne Skrypty Pythona z wnętrza kompilatora. Celem jest umożliwienie pisania wtyczek GCC w Pythonie. Wtyczka MELT zapewnia wysoki poziom języka Lispu do rozszerzenia GCC.
- pamięć transakcyjna C++ podczas kompilacji za pomocą-fgnu-tm.
- od GCC 10 identyfikatory zezwalają na kodowanie UTF-8 (Unicode), tzn. kod źródłowy C domyślnie używa kodowania UTF-8.