Wann ist es beim Entwerfen einer eigenen Programmiersprache sinnvoll, einen Konverter zu schreiben, der den Quellcode in C- oder C ++ - Code konvertiert, damit ich einen vorhandenen Compiler wie gcc verwenden kann, um den Maschinencode zu erhalten? Gibt es Projekte, die diesen Ansatz verwenden?
34
Antworten:
Die Übersetzung in C-Code ist eine sehr gut etablierte Gewohnheit. Das ursprüngliche C mit Klassen (und die frühen C ++ - Implementierungen, die damals als Cfront bezeichnet wurden ) haben dies erfolgreich durchgeführt. Mehrere Implementierungen von Lisp oder Scheme machen das, zB Chicken Scheme , Scheme48 , Bigloo . Einige Leute übersetzten Prolog zu C . Und einige Mozart- Versionen (und es gab Versuche, Ocaml-Bytecode nach C zu kompilieren ). Das CAIA-System mit künstlicher Intelligenz von J.Pitrat wird ebenfalls gebootet und generiert den gesamten C-Code. Vala übersetzt auch in C für GTK-bezogenen Code. Queinnecs Buch Lisp In Small Pieces habe ein Kapitel über die Übersetzung nach C.
Eines der Probleme bei der Übersetzung nach C sind schwanzrekursive Aufrufe . Der C-Standard garantiert nicht, dass ein C-Compiler sie richtig übersetzt (in einen "Sprung mit Argumenten", dh ohne Aufrufstapel zu essen), auch wenn in einigen Fällen neuere Versionen von GCC (oder von Clang / LLVM) diese Optimierung durchführen .
Ein weiteres Problem ist die Speicherbereinigung . Einige Implementierungen verwenden nur den konservativen Boehm-Garbage-Collector (der C-freundlich ist ...). Wenn Sie Code für die Garbage-Collection verwenden möchten (wie dies bei mehreren Lisp-Implementierungen der Fall ist, z. B. SBCL), könnte dies ein Albtraum sein (Sie möchten
dlclose
Posix verwenden).Ein weiteres Thema sind erstklassige Fortsetzungen und call / cc . Aber clevere Tricks sind möglich (siehe Chicken Scheme). Der Zugriff auf den Call-Stack kann viele Tricks erfordern (siehe GNU-Backtrace usw.). Das orthogonale Fortbestehen von Fortsetzungen (dh von Stapeln oder Fäden) wäre in C schwierig.
Die Ausnahmebehandlung ist oft eine Sache, um kluge Aufrufe an longjmp usw. zu senden.
Möglicherweise möchten Sie (in Ihrem ausgegebenen C-Code) entsprechende
#line
Anweisungen generieren . Dies ist langweilig undgdb
erfordert viel Arbeit (Sie möchten, dass z. B. einfacher zu debuggender Code erstellt wird).Mein MELT lispy domänenspezifische Sprache (anpassen oder erweitern GCC ) auf C (tatsächlich zu schlecht C ++ jetzt) übersetzt. Es hat einen eigenen Müllsammler. (Möglicherweise interessiert Sie Qish oder Ravenbrook MPS ). Generations-GC ist in maschinengeneriertem C-Code tatsächlich einfacher als in handgeschriebenem C-Code (da Sie Ihren C-Code-Generator auf Ihre Write-Barrier- und GC-Maschinerie zuschneiden).
Ich kenne keine Sprachimplementierung, die sich in echten C ++ - Code übersetzen lässt, dh mit Hilfe einer "Garbage Collection" -Technik zur Kompilierung von C ++ - Code, der viele STL-Vorlagen verwendet und die RAII- Redewendung respektiert . (Bitte geben Sie an, ob Sie einen kennen).
Was heute lustig ist, ist, dass C-Compiler (auf aktuellen Linux-Desktops) möglicherweise schnell genug sind, um eine in C übersetzte interaktive Top-Level- Lese-Evaluierungs-Druck-Schleife zu implementieren : Sie werden bei jedem Benutzer C-Code (einige hundert Zeilen) ausgeben Interaktion, Sie werden
fork
eine Zusammenstellung davon in ein gemeinsames Objekt, das Sie dann würdendlopen
. (MELT macht das alles fertig und es ist normalerweise schnell genug). All dies kann einige Zehntelsekunden dauern und für Endbenutzer akzeptabel sein.Nach Möglichkeit würde ich empfehlen, nach C zu übersetzen, nicht nach C ++, insbesondere weil die C ++ - Kompilierung langsam ist.
Wenn Sie Ihre Sprache implementieren, können Sie auch einige JIT- Bibliotheken wie libjit , GNU Lightning , asmjit oder sogar LLVM oder GCCJIT in Betracht ziehen (anstatt C-Code auszugeben) . Wenn Sie C übersetzen möchten, können Sie manchmal verwenden tinycc : es ist sehr schnell den generierten C - Code (auch im Speicher) kompiliert langsam Maschinencode. Im Allgemeinen möchten Sie jedoch die Optimierungen nutzen , die ein echter C-Compiler wie GCC vornimmt
Wenn Sie Ihre Sprache in C übersetzen, müssen Sie zunächst den gesamten AST des generierten C-Codes im Speicher erstellen (dies erleichtert auch das Generieren aller Deklarationen, dann aller Definitionen und des Funktionscodes). Auf diese Weise können Sie einige Optimierungen / Normalisierungen vornehmen. Sie könnten auch an mehreren GCC-Erweiterungen interessiert sein (z. B. computed gotos). Sie sollten es wahrscheinlich vermeiden, große C - Funktionen zu generieren - z. B. aus einer hunderttausenden Zeile generierten C - (Sie sollten sie besser in kleinere Teile aufteilen), da optimierte C - Compiler mit sehr großen C - Funktionen (in der Praxis) sehr unzufrieden sind experimentell,
gcc -O
Die Kompilierungszeit großer Funktionen ist proportional zum Quadrat der Funktionscodegröße. Begrenzen Sie daher die Größe Ihrer generierten C-Funktionen auf jeweils einige tausend Zeilen.Beachten Sie, dass C & C ++ - Compiler sowohl für Clang (über LLVM ) als auch für GCC (über libgccjit ) eine Möglichkeit bieten, einige für diese Compiler geeignete interne Repräsentationen zu emittieren. und ist spezifisch für jeden Compiler.
Wenn Sie eine Sprache entwerfen, die in C übersetzt werden soll, möchten Sie wahrscheinlich mehrere Tricks (oder Konstrukte) haben, um eine Mischung aus C und Ihrer Sprache zu generieren. Mein DSL2011-Papier MELT : Eine in den GCC-Compiler eingebettete übersetzte domänenspezifische Sprache sollte Ihnen nützliche Hinweise geben.
quelle
Es ist sinnvoll, wenn die Zeit zum Generieren des vollständigen Maschinencodes die Unannehmlichkeit überwiegt, einen Zwischenschritt zum Kompilieren Ihrer "IL" in Maschinencode mit einem C-Compiler zu haben.
Typischerweise werden domänenspezifische Sprachen auf diese Weise geschrieben. Ein System auf sehr hoher Ebene wird verwendet, um einen Prozess zu definieren oder zu beschreiben, der dann in eine ausführbare Datei oder eine DLL kompiliert wird. Die Zeit, die für die Erstellung einer funktionierenden / fehlerfreien Baugruppe benötigt wird, ist viel länger als für die Erstellung von C, und C ist dem Baugruppencode für die Leistung sehr ähnlich. Es ist daher sinnvoll, C zu generieren und die Fähigkeiten der C-Compiler-Autoren wiederzuverwenden. Beachten Sie, dass es nicht nur kompiliert, sondern auch optimiert wird - die Leute, die gcc oder llvm schreiben, haben viel Zeit damit verbracht, optimierten Maschinencode zu erstellen. Es wäre dumm zu versuchen, all ihre harte Arbeit neu zu erfinden.
Es ist möglicherweise akzeptabler, das Compiler-Backend von LLVM, dessen IIRC sprachneutral ist, erneut zu verwenden, sodass Sie LLVM-Anweisungen anstelle von C-Code generieren.
quelle
Das Schreiben eines Compilers zur Erzeugung von Maschinencode ist möglicherweise nicht viel schwieriger als das Schreiben eines Compilers, der C erzeugt (in einigen Fällen ist es auch einfacher). Ein Compiler, der Maschinencode erzeugt, kann jedoch nur ausführbare Programme auf der jeweiligen Plattform erzeugen, für die es wurde geschrieben; Im Gegensatz dazu kann ein Compiler, der C-Code erzeugt, Programme für jede Plattform erzeugen, die einen Dialekt von C verwendet, den der erzeugte Code unterstützen soll. Beachten Sie, dass es in vielen Fällen möglich sein kann, C-Code zu schreiben, der vollständig portierbar ist und sich wie gewünscht verhält, ohne Verhalten zu verwenden, das vom C-Standard nicht garantiert wird. Code, der auf plattformgarantierten Verhalten beruht, kann jedoch möglicherweise viel schneller ausgeführt werden auf Plattformen, die diese Garantien als Code machen, der dies nicht tut.
Angenommen, eine Sprache unterstützt ein Feature, mit dem
UInt32
aus vier aufeinanderfolgenden Bytes eines willkürlich ausgerichteten Ausdrucks eineUInt8[]
Big-Endian-Interpretation erstellt werden kann. Auf einigen Compilern könnte man den Code wie folgt schreiben:und veranlassen Sie den Compiler, eine Wortladeoperation zu generieren, gefolgt von einer Anweisung, die Byte in Wort umkehrt. Einige Compiler unterstützen den Modifikator __packed jedoch nicht und generieren in Abwesenheit Code, der nicht funktioniert.
Alternativ könnte man den Code schreiben als:
Ein solcher Code sollte auf jeder Plattform funktionieren, auch auf solchen, auf denen
CHAR_BITS
nicht 8 vorhanden ist (vorausgesetzt, dass jedes Oktett der Quelldaten in einem bestimmten Array-Element endet), aber ein solcher Code läuft wahrscheinlich nicht annähernd so schnell wie der nicht tragbare Code Version auf Plattformen, die die erstere unterstützen.Beachten Sie, dass die Portabilität häufig erfordert, dass der Code bei Typecasts und ähnlichen Konstrukten äußerst liberal ist. Zum Beispiel muss Code, der zwei vorzeichenlose 32-Bit-Ganzzahlen multiplizieren und die unteren 32 Bit des Ergebnisses liefern möchte, für die Portabilität wie folgt geschrieben werden:
Ohne dies könnte
1u
ein Compiler auf einem System, auf dem INT_BITS im Bereich von 33 bis 64 liegt, legitimerweise alles tun, was er möchte, wenn das Produkt von x und y größer als 2.147.483.647 ist, und einige Compiler sind geneigt, solche Möglichkeiten zu nutzen.quelle
Sie haben oben einige ausgezeichnete Antworten gegeben, aber in einem Kommentar haben Sie die Frage "Warum möchten Sie überhaupt eine eigene Programmiersprache erstellen?" Mit "Es ist hauptsächlich zu Lernzwecken gedacht" beantwortet. Ich werde aus einem anderen Blickwinkel antworten.
Es ist sinnvoll, einen Konverter zu schreiben, der den Quellcode in C- oder C ++ - Code konvertiert, damit Sie einen vorhandenen Compiler wie gcc verwenden können, um Maschinencode zu erhalten, wenn Sie mehr über Lexika, Syntax und Funktionen erfahren möchten semantische Analyse, als Sie über die Codegenerierung und -optimierung lernen!
Das Schreiben eines eigenen Maschinencode-Generators ist eine ziemlich bedeutende Arbeit, die Sie vermeiden können, indem Sie C-Code kompilieren, wenn Sie sich nicht in erster Linie dafür interessieren!
Wenn Sie sich jedoch für das Assemblerprogramm interessieren und von den Herausforderungen der Codeoptimierung auf der untersten Ebene fasziniert sind, schreiben Sie auf jeden Fall selbst einen Code-Generator für die Lernerfahrung!
quelle
Es hängt davon ab, welches Betriebssystem Sie verwenden, wenn Sie Windows verwenden. Es gibt eine Microsoft IL (Intermediate Language), die Ihren Code in eine Zwischensprache konvertiert, sodass es keine Zeit kostet, in Maschinencode kompiliert zu werden. Oder Wenn Sie Linux verwenden, gibt es dafür einen separaten Compiler
Wenn Sie beim Entwerfen Ihrer eigenen Sprache auf Ihre Frage zurückkommen, sollten Sie einen separaten Compiler oder Interpreter dafür haben, da der Computer die Hochsprache nicht kennt. Ihr Code sollte in Maschinencode kompiliert werden, damit er für die Maschine nützlich ist
quelle
Your code should be compiled into machine code to make it useful for machine
- Wenn Ihr Compiler C-Code als Ausgabe erzeugt, könnten Sie den C-Code in einen C-Compiler einfügen, um Maschinencode zu erzeugen, oder?