Wann ist es sinnvoll, zuerst meine eigene Sprache in C-Code zu kompilieren?


34

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?



4
Wenn Sie nach C schauen, werden Sie feststellen, dass C # und Java auch in Zwischensprachen kompiliert werden. Sie müssen nicht mehr viel tun, was bereits jemand anderes getan hat, indem Sie auf eine Zwischensprache abzielen, anstatt direkt zum Zusammenbau zu wechseln.
Casey

1
@emodendroket C # und Java werden jedoch zu einer IL kompiliert, die im Allgemeinen und für C # / Java im Besonderen als IL konzipiert ist. Daher sind CIL- und JVM-Bytecode in vielerlei Hinsicht sinnvoller und praktischer als eine IL, die C jemals sein könnte. Es geht nicht darum, ob eine Zwischensprache verwendet werden soll, sondern darum, welche Zwischensprache verwendet werden soll.

1
Schauen Sie sich verschiedene Implementierungen freier Software an, die C-Code generieren. Und ich hoffe, Sie werden Ihre Sprachimplementierung zu einer kostenlosen Software machen.
Basile Starynkevitch

2
Hier ist der aktualisierte Link von @ RobertHarveys Kommentar: yosefk.com/blog/c-as-an-intermediate-language.html .
Christian Dean

Antworten:


52

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 dlclosePosix 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 #lineAnweisungen generieren . Dies ist langweilig und gdberfordert 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 forkeine Zusammenstellung davon in ein gemeinsames Objekt, das Sie dann würden dlopen. (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 -ODie 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.


Beziehen Sie sich auf "Hühnerschema"?
Robert Harvey

1
Ja. Ich habe die URL angegeben.
Basile Starynkevitch

Ist es relativ praktisch, eine virtuelle Maschine wie Java oder so etwas zu erstellen, Bytecode nach C zu kompilieren und dann gcc für die JIT-Kompilierung zu verwenden? Oder sollten sie einfach direkt vom Bytecode zur Assembly wechseln?
Panzercrisis

1
@Panzercrisis Die meisten JIT-Compiler benötigen ihre Maschinencode-Backends, um Funktionen zu ersetzen und vorhandenen Code durch eine Sprung- / Falltür zu patchen. Abgesehen davon ist gcc spezifisch ... architektonisch weniger für die JIT-Kompilierung und andere Anwendungsfälle geeignet. Schauen Sie sich libgccjit an: gcc.gnu.org/ml/gcc-patches/2013-10/msg00228.html und gcc.gnu.org/wiki/JIT

1
Tolles Orientierungsmaterial. Vielen Dank!
15.

7

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.


Es scheint, als wären Bibliotheken ein ziemlich zwingender Grund, dies ebenfalls in Betracht zu ziehen.
Casey

Wenn Sie "Ihre IL" sagen, worauf beziehen Sie sich? Ein abstrakter Syntaxbaum?
Robert Harvey

@ Robert Harvey Nein, ich meine C-Code. Im Fall von OPs ist dies eine Zwischensprache auf halbem Weg zwischen seiner eigenen Hochsprache und dem Maschinencode. Ich habe es in Anführungszeichen gesetzt, um diese Idee zu vermitteln, dass es nicht IL ist, wie es von vielen Leuten verwendet wird (z. B. Microsoft's .NET IL)
gbjbaanb

2

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 UInt32aus vier aufeinanderfolgenden Bytes eines willkürlich ausgerichteten Ausdrucks eine UInt8[]Big-Endian-Interpretation erstellt werden kann. Auf einigen Compilern könnte man den Code wie folgt schreiben:

uint32_t dat = *(__packed uint32_t*)p;
return (dat >> 24) | (dat >> 8) | ((uint32_t)dat << 8) | ((uint32_t)dat << 24));

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:

return dat[3] | ((uint16_t)dat[2] << 8) | ((uint32_t)dat[1] << 16) | ((uint32_t)dat[0] << 24);

Ein solcher Code sollte auf jeder Plattform funktionieren, auch auf solchen, auf denen CHAR_BITSnicht 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:

uint32_t result = 1u*x*y;

Ohne dies könnte 1uein 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.


1

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!


-7

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


2
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?
Robert Harvey

Ja. weil Maschine nicht die Sprache C
Tayyab Gulsher Vohra

2
Recht. Die Frage war also: "Wann ist es sinnvoll, c auszugeben und einen AC-Compiler zu verwenden, anstatt Maschinensprache oder Bytecode direkt auszugeben?"
Robert Harvey

Eigentlich möchte er seine Programmiersprache entwerfen, in der er fragt, ob er sie in C- oder C ++ - Code konvertieren soll. Deshalb erkläre ich dies, wenn Sie Ihre eigene Programmiersprache entwerfen, warum Sie den C-Compiler oder C ++ verwenden sollten. wenn du intelligent genug bist, solltest du dein eigenes
designen

8
Ich glaube nicht, dass du die Frage verstehst. Siehe yosefk.com/blog/c-as-an-intermediate-language.html
Robert Harvey
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.