Obwohl es für Entwickler üblich ist, Programme in einer "Hochsprache" wie Python oder Java zu erstellen, müssen diese Programme kompiliert werden, um die Fähigkeiten des Mikroprozessors direkt zu nutzen. LLVM ist in diesem Bereich sehr innovativ, da es Aspekte wie Modularität und Echtzeitkompilierung fördert.
Entwickler wissen es… Das Schreiben einer Reihe von Anweisungen in einer Sprache wie Python, Java oder C++ ist nur ein Schritt in dem Prozess, der zu einem ausführbaren Programm führt. Der Prozess, der den „Quellcode“ (das, was der Entwickler schreibt) in einen Code umwandelt, der von einem Mikroprozessor verarbeitet werden kann, wird Kompilieren genannt.
Sprachen wie Python oder Java werden als Hochsprachen bezeichnet. Sie enthalten Anweisungen, die für einen Menschen leicht verständlich sind, wie z. B. das berühmte IF, das zum Testen einer Bedingung verwendet wird und in den meisten Sprachen vorkommt. Die Entwicklung eines Programms in Maschinencode ist äußerst schwierig, daher werden die in einer Hochsprache geschriebenen Anweisungen beim Kompilieren in Maschinencode umgewandelt.
LLVM wurde von dem Informatiker Chris Lattner um 2002 an der Universität von Illinois im Rahmen seiner Doktorarbeit entwickelt und sollte den Ansatz des Kompilierens erneuern und seine Verwendung optimieren.
Optimierung des Kompilierungsprozesses
Das Bestreben, Compiler zu optimieren, ist nicht neu. Der GCC-Compiler – ein Teil des GNU-Betriebssystems (für Linux), der 1984 von Richard Stallman eingeführt wurde – hat im Laufe der Zeit viele Optimierungsoptionen eingebaut, um die Effizienz des erzeugten Codes zu verbessern.
LLVM wurde Anfang der 2000er Jahre entwickelt, als Multi-Core-Mikroprozessor-Architekturen und GPUs oder Grafikprozessoren in Computern üblich wurden.
LLVM ist die Abkürzung für Low Level Virtual Machine, und dieser Name ist ein Teil dessen, was es tut: virtuelle Maschinen (die das Verhalten bestimmter Prozessoren emulieren können) auf einer niedrigen Ebene (und damit nahe am Prozessor) zu erstellen.
Der innovative Ansatz von LLVM
LLVM hat daher verschiedene innovative Ansätze vorgeschlagen:
- eine modulare Architektur ;
- die JIT-Kompilierung ;
- eine Intermediäre Darstellung IR ;
- ein ganzes Ökosystem von wiederverwendbaren Werkzeugen.
Wir werden diese verschiedenen Punkte durchgehen.
Modulare Architektur
Traditionell bestanden die meisten Compiler aus einem einzigen Programm, was es schwierig machte, sie zu optimieren oder ihre Fähigkeiten zu erweitern.
Eines der Hauptziele für LLVM war es, eine modulare Compiler-Architektur zu schaffen, d. h. verschiedene Komponenten dazu zu bringen, harmonisch zusammenzuarbeiten. Daraus ergaben sich zahlreiche Vorteile.
Die Entwicklung des Compilers kann von verschiedenen Teams durchgeführt werden, die jeweils ein oder mehrere bestimmte Module erstellen. Dadurch wird die Entwicklungszeit verkürzt und die Gesamteffizienz gesteigert. Der von einem Team erstellte Code ist in der Regel sauberer und für diejenigen, die ihn warten sollen, leichter zu verstehen.
Wenn ein Compiler aus einer Reihe von Modulen besteht, können einzelne Module separat optimiert werden. Und da die Wartung auf diesem isolierten Modul durchgeführt werden kann. Daraus ergibt sich eine größere Flexibilität.
Wenn sich im Laufe der Monate herausstellt, dass dem Compiler eine wichtige Funktion fehlt, kann diese hinzugefügt werden, sei es durch Eingriffe in eines der bestehenden Module oder durch die Erstellung eines neuen Moduls. Die modulare Struktur fördert somit das Experimentieren und die Implementierung neuer Fähigkeiten.
Der modulare Aufbau erleichtert die Integration externer Tools.
JIT-Kompilierung oder "on the fly"-Kompilierung
Bei der traditionellen Kompilierung wird die gesamte Umwandlung vor der Ausführung des Programms vorgenommen, wodurch ein endgültiger Maschinencode entsteht, der nicht mehr verändert werden kann.
Bei der JIT-Kompilierung (Just-In-Time) von LLVM wird der vom Entwickler geschriebene Quellcode zur Laufzeit in ausführbaren Maschinencode umgewandelt.
Die JIT-Kompilierung erfolgt unmittelbar vor der Ausführung einer Sequenz von Maschinencode.
Dies wird als „selektive Kompilierung“ bezeichnet. Die Optimierung erfolgt dynamisch (in Echtzeit), je nach dem Verhalten des Programms.
LLVM ist nicht das einzige Werkzeug, das die JIT-Kompilierung verwendet. Dasselbe gilt für .Net (Microsoft) und viele Java-Implementierungen.
IR oder Zwischenvertretung
Wie wir oben gesehen haben, steht LLVM für „Low Level Virtual Machines“. Und hier haben wir einen weiteren Schlüssel zu LLVM: Es erzeugt keinen Code, der auf einen bestimmten Mikroprozessor zugeschnitten ist, sondern einen Zwischencode, der der Maschinensprache ähnelt, aber unabhängig von einer bestimmten Recheneinheit ist.
Diese „intermediate representation“ (IR) kann an alle Arten von Prozessoren angepasst werden, egal ob es sich um Recheneinheiten oder Grafikprozessoren handelt. Auch dieser Ansatz hat wieder viele Vorteile:
- IR kann unabhängig vom endgültigen, prozessorspezifischen Code optimiert werden.
- Es ist einfacher, den IR-Code zu analysieren oder zu debuggen als die Maschinensprache.
- Wenn ein neuer Prozessor auf den Markt kommt, ist es einfach, den Compiler dazu zu bringen, den entsprechenden Typ von Maschinensprache zu integrieren.
Ein reichhaltiges Ökosystem
LLVM profitiert von einem breiten Ökosystem an Tools und Erweiterungen, die seine Fähigkeiten erweitern können. Dies ist zum Teil auf die Präsenz von qualitativ hochwertigen Benutzergemeinschaften zurückzuführen. Zum Beispiel wird der Clang-Compiler (der auf LLVM für Sprachen wie C, C++ und Objective-C basiert) aktiv von Mitarbeitern von Google, Apple, Mozilla und ARM unterstützt. Auch neue und sehr erfolgreiche Sprachen wie Rust und Swift, die ebenfalls auf LLVM basieren, verfügen über große und engagierte Communities.