Überblick über die erweiterte Kompilierungspipeline von GCC, einschließlich spezieller Programme wie Präprozessor, Assembler und Linker.
GCC folgt der für mehrsprachige und Multi-CPU-Compiler typischen 3-stufigen Architektur. Alle Programmbäume werden am „mittleren Ende“ in eine gemeinsame abstrakte Darstellung konvertiert, sodass Codeoptimierung und binäre Codegenerierungsfunktionen von allen Sprachen gemeinsam genutzt werden können.,
Die externe Schnittstelle von GCC folgt Unix-Konventionen. Benutzer rufen ein sprachspezifisches Treiberprogramm auf (gcc
für C, g++
für C++ usw.), das Befehlsargumente interpretiert, den eigentlichen Compiler aufruft, den Assembler für die Ausgabe ausführt und dann optional den Linker ausführt, um eine vollständige ausführbare Binärdatei zu erstellen.
Jeder der Sprachkompiler ist ein separates Programm, das Quellcode liest und Maschinencode ausgibt. Alle haben eine gemeinsame interne Struktur., Ein sprachübergreifendes Frontend analysiert den Quellcode in dieser Sprache und erzeugt einen abstrakten Syntaxbaum (kurz“Baum“).
Diese werden bei Bedarf in die Eingabedarstellung des mittleren Endes umgewandelt, die als GENERISCHE Form bezeichnet wird; Das mittlere Ende transformiert das Programm dann schrittweise in seine endgültige Form. Compiler-Optimierungen und statische Codeanalysetechniken (wie FORTIFY_SOURCE, eine Compiler-Direktive, die versucht, einige Pufferüberläufe zu ermitteln) werden auf den Code angewendet., Diese arbeiten an mehreren Darstellungen, meist der architekturunabhängigen GIMPLE-Darstellung und der architekturabhängigen RTL-Darstellung. Schließlich wird Maschinencode unter Verwendung eines architekturspezifischen Mustervergleichs erstellt, der ursprünglich auf einem Algorithmus von Jack Davidson und Chris Fraser basiert.
GCC wurde hauptsächlich in C geschrieben, mit Ausnahme von Teilen des Ada-Frontends. Die Distribution umfasst die Standardbibliotheken für Ada, C++ und Java, deren Code hauptsächlich in diesen Sprachen geschrieben ist., Auf einigen Plattformen enthält die Distribution auch eine Low-Level-Laufzeitbibliothek, libgcc, die in einer Kombination aus maschinenunabhängigem C und prozessorspezifischem Maschinencode geschrieben ist und hauptsächlich für arithmetische Operationen entwickelt wurde,die der Zielprozessor nicht direkt ausführen kann.
GCC verwendet in seinem Build viele Standardwerkzeuge, darunter Perl, Flex, Bison und andere gängige Tools. Darüber hinaus sind zum Erstellen derzeit drei zusätzliche Bibliotheken erforderlich: GMP, MPC und MPFR.
Im Mai 2010 beschloss der GCC-Lenkungsausschuss, die Verwendung eines C++ – Compilers zum Kompilieren von GCC zuzulassen., Der Compiler sollte hauptsächlich in C plus einer Teilmenge von Funktionen aus C++geschrieben werden. Dies wurde insbesondere beschlossen, damit die Entwickler von GCC die Destruktoren und Generika-Funktionen von C++verwenden konnten.
Im August 2012 gab das GCC Steering Committee bekannt, dass GCC jetzt C++ als Implementierungssprache verwendet. Dies bedeutet, dass zum Erstellen von GCC aus Quellen ein C++ – Compiler erforderlich ist, der den ISO/IEC C++03-Standard versteht.,
Frontendsedit
Frontends bestehen aus Vorverarbeitung, lexikalischer Analyse, syntaktischer Analyse (Parsing) und semantischer Analyse. Die Ziele der Compiler-Frontends bestehen darin, Kandidatenprogramme gemäß der Sprachgrammatik und-semantik entweder zu akzeptieren oder abzulehnen, Fehler zu identifizieren und gültige Programmdarstellungen für spätere Compiler-Phasen zu behandeln. Dieses Beispiel zeigt die Lexer-und Parserschritte, die für ein einfaches Programm in C ausgeführt wurden.,
Jedes Frontend verwendet einen Parser, um den abstrakten Syntaxbaum einer bestimmten Quelldatei zu erstellen. Aufgrund der Syntaxbaumabstraktion können Quelldateien einer der verschiedenen unterstützten Sprachen vom selben Backend verarbeitet werden. GCC begann mit der Verwendung von LALR-Parsern, die mit Bison generiert wurden, wechselte jedoch nach und nach zu handgeschriebenen rekursiven Abstiegsparsern für C++ im Jahr 2004 und für C und Objective-C im Jahr 2006. Ab 2021 verwenden alle Frontends handgeschriebene Parser für rekursiven Abstieg.
Bis GCC 4.,0 die Baumdarstellung des Programms war nicht vollständig unabhängig vom angestrebten Prozessor. Die Bedeutung eines Baumes war für verschiedene Sprach-Frontends etwas anders, und Frontends konnten ihre eigenen Baumcodes bereitstellen. Dies wurde mit der Einführung von GENERIC und GIMPLE vereinfacht, zwei neuen Formen sprachunabhängiger Bäume, die mit dem Aufkommen von GCC 4.0 eingeführt wurden. GENERIC ist komplexer, basierend auf dem GCC 3.x Java-Frontend-Zwischendarstellung. GIMPLE ist ein vereinfachtes GENERIKUM, bei dem verschiedene Konstrukte auf mehrere GIMPLE-Anweisungen reduziert werden., Die C -, C++ – und Java-Frontends erzeugen Generika direkt im Frontend. Andere Frontends haben stattdessen nach dem Parsen unterschiedliche Zwischendarstellungen und konvertieren diese in GENERISCHE.
In beiden Fällen wandelt der sogenannte „gimplifier“ dieses komplexere Formular dann in das einfachere SSA-basierte GIMPLE-Formular um, das die gemeinsame Sprache für eine Vielzahl leistungsfähiger sprach – und architekturunabhängiger globaler Optimierungen ist (Funktionsumfang).,
GENERISCHE und GIMPLEEdit
GENERIKA ist eine intermediate-Repräsentation verwendete Sprache als eine „nahen Ende“, während das kompilieren von Quellcode in ausführbaren Binärdateien. Eine Teilmenge namens GIMPLE wird von allen Frontends von GCC angestrebt.
Die mittlere Stufe von GCC führt die gesamte Codeanalyse und-optimierung durch und arbeitet unabhängig von der kompilierten Sprache und der Zielarchitektur, beginnend mit der GENERISCHEN Darstellung und erweitert sie auf die Registerübertragungssprache (RTL)., Die generische Darstellung enthält nur die Teilmenge der imperativen Programmierkonstrukte, die vom mittleren Ende optimiert wurden.
Bei der Umwandlung des Quellcodes in GIMPLE werden komplexe Ausdrücke mithilfe temporärer Variablen in einen Code mit drei Adressen aufgeteilt. Diese Darstellung wurde von der EINFACHEN Darstellung inspiriert, die Laurie J. Hendren im McCAT-Compiler vorgeschlagen hatte, um die Analyse und Optimierung von imperativen Programmen zu vereinfachen.,
OptimizationEdit
Optimierung kann während jeder Phase der Kompilierung erfolgen; Der Großteil der Optimierungen erfolgt jedoch nach der Syntax-und Semantikanalyse des Frontends und vor der Codegenerierung des Backends.Somit ist ein allgemeiner, wenn auch etwas widersprüchlicher Name für diesen Teil des Compilers das „mittlere Ende.“
Der genaue Satz von GCC-Optimierungen variiert von Release zu Release, während er sich entwickelt, umfasst jedoch die Standardalgorithmen wie Schleifenoptimierung, Sprungthreading, allgemeine Eliminierung von Unterausdrücken, Befehlsplanung usw., Die RTL-Optimierungen sind von geringerer Bedeutung mit dem Zusatz von globalen SSA-basierten Optimierungen auf GIMPLE Bäume, da RTL-Optimierungen haben einen viel begrenzten Umfang, und haben weniger High-Level-Informationen.
Einige dieser Optimierungen, die auf dieser Ebene durchgeführt werden, umfassen die Eliminierung von totem Code, die Eliminierung von partieller Redundanz, die globale Wertnummerierung, die spärliche bedingte konstante Ausbreitung und den skalaren Ersatz von Aggregaten. Array – basierte Optimierungen wie automatische Vektorisierung und automatische Parallelisierung werden ebenfalls durchgeführt. Profil-geführte Optimierung ist möglich.,
Backendedit
Das Backend des GCC wird teilweise durch Präprozessormakros und Funktionen angegeben, die für eine Zielarchitektur spezifisch sind, um beispielsweise seine Endianness, Wortgröße und Aufrufkonventionen zu definieren., Der vordere Teil des Backends verwendet diese, um die RTL-Generierung zu entscheiden, so dass, obwohl GCCS RTL nominell prozessorunabhängig ist, die anfängliche Sequenz abstrakter Anweisungen bereits an das Ziel angepasst ist. Die eigentlichen RTL-Anweisungen, die die Programmdarstellung bilden, müssen jederzeit der Maschinenbeschreibung der Zielarchitektur entsprechen.
Die Maschinenbeschreibungsdatei enthält RTL-Muster sowie Operanden-Einschränkungen und Codeausschnitte zur Ausgabe der Endmontage., Die Einschränkungen zeigen an, dass ein bestimmtes RTL-Muster möglicherweise nur (z. B.) für bestimmte Hardwareregister gilt oder (z. B.) sofortige Operanden-Offsets nur einer begrenzten Größe zulässt (z. B. 12, 16, 24, … bit-offsets, etc.). Während der RTL-Generierung werden die Einschränkungen für die angegebene Zielarchitektur überprüft. Um ein bestimmtes RTL-Snippet auszugeben, muss es mit einem (oder mehreren) der RTL-Muster in der Maschinenbeschreibungsdatei übereinstimmen und die Einschränkungen für dieses Muster erfüllen.Andernfalls wäre es unmöglich, die endgültige RTL in Maschinencode zu konvertieren.,
Gegen Ende der Kompilierung wird gültiges RTL auf eine strenge Form reduziert, in der sich jeder Befehl auf echte Maschinenregister und ein Muster aus der Maschinenbeschreibungsdatei des Ziels bezieht. Die Bildung einer strengen RTL ist eine komplizierte Aufgabe; Ein wichtiger Schritt ist die Registerzuweisung, bei der echte Hardwareregister ausgewählt werden, um die ursprünglich zugewiesenen Pseudoregister zu ersetzen. Es folgt eine Phase des „Nachladens“; Alle Pseudoregister, denen kein echtes Hardwareregister zugewiesen wurde, werden auf den Stapel „verschüttet“, und RTL, um dieses Verschütten durchzuführen, wird generiert., Ebenso müssen Offsets, die zu groß sind, um in einen tatsächlichen Befehl zu passen, aufgebrochen und durch RTL-Sequenzen ersetzt werden, die den Offset-Einschränkungen entsprechen.
In der Endphase wird der Maschinencode erstellt, indem ein kleiner Codeausschnitt aufgerufen wird, der jedem Muster zugeordnet ist, um die tatsächlichen Anweisungen aus dem Befehlssatz des Ziels unter Verwendung der endgültigen Register, Offsets und Adressen zu generieren während der Reload-Phase ausgewählt. Das Assembly-Generierungs-Snippet kann nur eine Zeichenfolge sein, in diesem Fall wird eine einfache Zeichenfolgenersetzung der Register, Offsets und/oder Adressen in die Zeichenfolge durchgeführt., Das Assembly-Generierungs-Snippet kann auch ein kurzer Block von C-Code sein, der einige zusätzliche Arbeiten ausführt, aber letztendlich eine Zeichenfolge zurückgibt, die den gültigen Assembly-Code enthält.
Andere featuresEdit
Einige Funktionen von GCC sind:
- Link-time optimization optimiert über Objektdateigrenzen hinweg, um die verknüpfte Binärdatei direkt zu verbessern. Die Linkzeitoptimierung beruht auf einer Zwischendatei, die die Serialisierung einer in der Objektdatei enthaltenen Gimple-Darstellung enthält. Die Datei wird zusammen mit der Objektdatei während der Quellkompilierung generiert., Jede Quellkompilierung generiert eine separate Objektdatei und eine Verknüpfungszeithilfedatei. Wenn die Objektdateien verknüpft sind, wird der Compiler erneut ausgeführt und verwendet die Hilfedateien, um den Code für die separat kompilierten Objektdateien zu optimieren.
- Plugins können den GCC Compiler direkt erweitern. Plugins ermöglichen es einem Standard-Compiler, durch externen Code, der als Plugins geladen wird, auf bestimmte Bedürfnisse zugeschnitten zu werden. Plugins können beispielsweise Middle-End-Pässe hinzufügen, ersetzen oder sogar entfernen, die mit Gimple-Darstellungen arbeiten., Es wurden bereits mehrere GCC-Plugins veröffentlicht, insbesondere das GCC Python-Plugin, das mit libpython verknüpft ist und es ermöglicht, beliebige Python-Skripte aus dem Compiler heraus aufzurufen. Ziel ist es, GCC-Plugins in Python schreiben zu lassen. Das MELT-Plugin bietet eine Lisp-ähnliche Sprache auf hoher Ebene, um GCC zu erweitern.
- C++ Transaktionsspeicher beim Kompilieren mit-fgnu-tm.
- Ab GCC 10 erlauben Bezeichner die UTF-8-Codierung (Unicode), dh der C-Quellcode verwendet standardmäßig die UTF-8-Codierung.