C ++: Metaprogrammierung mit einer Compiler-API anstelle von C ++ - Funktionen

10

Dies begann als SO-Frage, aber ich erkannte, dass es ziemlich unkonventionell ist und basierend auf der tatsächlichen Beschreibung auf den Websites möglicherweise besser für Programmierer geeignet ist, da die Frage viel konzeptionelles Gewicht hat.

Ich habe Clang LibTooling gelernt und es ist ein sehr leistungsfähiges Tool, das in der Lage ist, das gesamte " Nitty Gritty " des Codes auf freundliche Weise, dh auf semantische Weise und auch nicht durch Vermutung, aufzudecken. Wenn clang Ihren Code kompilieren kann, ist clang sicher über die Semantik jedes einzelnen Zeichens in diesem Code.

Lassen Sie mich jetzt einen Moment zurücktreten.

Es gibt viele praktische Probleme, die auftreten, wenn man sich mit der Metaprogrammierung von C ++ - Vorlagen beschäftigt (und insbesondere, wenn man sich über Vorlagen hinaus in das Gebiet cleverer, wenn auch erschreckender Makros wagt). Um ehrlich zu sein, sind viele der üblichen Verwendungszwecke von Vorlagen für viele Programmierer, auch für mich, etwas erschreckend.

Ich denke, ein gutes Beispiel wären Zeichenfolgen zur Kompilierungszeit . Dies ist eine Frage, die mittlerweile über ein Jahr alt ist, aber es ist klar, dass C ++ ab sofort dies für bloße Sterbliche nicht einfach macht. Das Betrachten dieser Optionen reicht zwar nicht aus, um Übelkeit für mich auszulösen, aber ich bin mir nicht sicher, ob ich magischen, maximal effizienten Maschinencode erstellen kann, der zu jeder ausgefallenen Anwendung passt, die ich für meine Software habe.

Ich meine, seien wir ehrlich, Leute, Saiten sind ziemlich einfach und grundlegend. Einige von uns möchten nur einen bequemen Weg, um Maschinencode auszugeben, bei dem bestimmte Zeichenfolgen wesentlich stärker "eingebrannt" sind, als dies beim einfachen Codieren der Fall ist. In unserem C ++ - Code.

Geben Sie clang und LibTooling ein, wodurch der abstrakte Syntaxbaum (AST) des Quellcodes verfügbar gemacht wird und eine einfache benutzerdefinierte C ++ - Anwendung die korrekte und zuverlässige Bearbeitung des Rohquellcodes (mithilfe Rewriter) zusammen mit einem umfangreichen semantischen objektorientierten Modell von allem im AST ermöglicht. Es behandelt viele Dinge. Es kennt die Makroerweiterungen und lässt Sie diesen Ketten folgen. Ja, ich spreche von Transformation oder Übersetzung von Quellcode zu Quellcode.

Meine grundlegende These hier ist, dass Clang es uns jetzt ermöglicht, ausführbare Dateien zu erstellen, die selbst als ideale benutzerdefinierte Präprozessorstufen für unsere C ++ - Software fungieren können, und wir können diese Metaprogrammierstufen mit C ++ implementieren. Wir sind lediglich durch die Tatsache eingeschränkt, dass diese Phase Eingaben vornehmen muss, die gültigen C ++ - Code sind, und als Ausgabe mehr gültigen C ++ - Code erzeugen muss. Plus alle anderen Einschränkungen, die Ihr Build-System anwendet.

Die Eingabe muss zumindest sehr nahe am gültigen C ++ - Code liegen, denn schließlich ist clang das Compiler-Frontend, und wir stöbern nur herum und sind kreativ mit seiner API. Ich weiß nicht, ob es eine Möglichkeit gibt, eine neue zu verwendende Syntax zu definieren, aber wir müssen natürlich die Methoden entwickeln, um sie richtig zu analysieren und dem Clang-Projekt hinzuzufügen, um dies zu tun. Mehr zu erwarten bedeutet, etwas im Clang-Projekt zu haben, das außerhalb des Rahmens liegt.

Kein Problem. Ich würde mir vorstellen, dass einige No-Op-Makrofunktionen diese Aufgabe übernehmen können.

Eine andere Möglichkeit, das zu betrachten, was ich beschreibe, besteht darin, Metaprogrammierkonstrukte mit Laufzeit-C ++ zu implementieren, indem der AST unseres Quellcodes (dank clang und seiner API) manipuliert wird, anstatt sie mit den eingeschränkteren Tools zu implementieren, die in der Sprache selbst verfügbar sind. Dies hat auch deutliche Vorteile für die Kompilierungsleistung (vorlagenintensive Header verlangsamen die Kompilierung proportional zu der Häufigkeit, mit der Sie sie verwenden. Viele kompilierte Inhalte werden dann sorgfältig abgeglichen und vom Linker weggeworfen).

Dies geht jedoch zu Lasten der Einführung von ein oder zwei zusätzlichen Schritten in den Erstellungsprozess und auch der Anforderung, eine (zugegebenermaßen) etwas ausführlichere Software (aber zumindest eine einfache Laufzeit-C ++) als Teil unseres Tools zu schreiben .

Das ist nicht das ganze Bild. Ich bin mir ziemlich sicher, dass durch das Generieren von Code, der mit Kernsprachenfunktionen äußerst schwierig oder unmöglich ist, ein viel größerer Funktionsbereich zur Verfügung steht. In C ++ können Sie eine Vorlage oder ein Makro oder eine verrückte Kombination aus beiden schreiben, aber in einem Clang-Tool können Sie Klassen und Funktionen auf JEDE Weise ändern, die Sie mit C ++ zur Laufzeit erreichen können , während Sie vollen Zugriff auf den semantischen Inhalt haben. Neben Vorlagen und Makros und allem anderen.

Ich frage mich also, warum das nicht schon alle machen. Ist es so, dass diese Funktionalität von clang so neu ist und niemand mit der riesigen Klassenhierarchie von clangs AST vertraut ist? Das kann es nicht sein.

Vielleicht unterschätze ich die Schwierigkeit nur ein wenig, aber die "Manipulation von Zeichenfolgen zur Kompilierungszeit" mit einem Clang-Tool ist fast kriminell einfach. Es ist ausführlich, aber wahnsinnig einfach. Alles, was benötigt wird, sind eine Reihe von No-Op-Makrofunktionen, die den tatsächlichen realen std::stringOperationen zugeordnet sind. Das Clang-Plugin implementiert dies, indem es alle relevanten No-Op-Makroaufrufe abruft und die Operationen mit Zeichenfolgen ausführt. Dieses Tool wird dann als Teil des Erstellungsprozesses eingefügt. Während der Erstellung werden diese No-Op-Makrofunktionsaufrufe automatisch in ihren Ergebnissen ausgewertet und dann als einfache alte Zeichenfolgen zur Kompilierungszeit wieder in das Programm eingefügt. Das Programm kann dann wie gewohnt kompiliert werden. Tatsächlich ist dieses resultierende Programm daher auch viel portabler, da kein ausgefallener neuer Compiler erforderlich ist, der C ++ 11 unterstützt.

Steven Lu
quelle
Dies ist eine ungewöhnlich lange Frage. Könnten Sie es vielleicht auf Ihre wichtigsten Punkte zusammenfassen?
Amon
Ich poste viele lange Fragen. Aber gerade bei diesem sind alle Teile der Frage wichtig, denke ich. Vielleicht die ersten 6 Absätze überspringen? Haha.
Steven Lu
3
Klingt sehr nach den syntaktischen Makros, die in Lisp entwickelt wurden und kürzlich von Haxe, Nemerle, Scala und ähnlichen Sprachen aufgenommen wurden. Es wird viel darüber gelesen, warum Lisp-Makros als schädlich angesehen werden. Obwohl ich noch kein überzeugendes Argument gehört habe, finden Sie vielleicht Gründe, warum die Leute nicht bereit waren, sie jeder Sprache hinzuzufügen (abgesehen von der Tatsache, dass es nicht unbedingt einfach ist).
back2dos
Ja, es ist meta-ifying C ++. Was besseren, schnelleren Code bedeuten kann. Wie für diese Sprachen. Nun, wo soll ich anfangen? Was ist ein Multi-Millionen-Dollar-Videospiel, das in einer dieser Sprachen implementiert ist? Was ist ein moderner Webbrowser, der in einer dieser Sprachen implementiert ist? Betriebssystemkernel? Okay, es scheint tatsächlich so, als ob Haxe etwas Traktion hat, aber Sie haben die Idee.
Steven Lu
1
@nwp, Nun, ich kann nicht anders, als darauf hinzuweisen, dass Sie anscheinend den gesamten Punkt des Beitrags verpasst haben. Zeichenfolgen zur Kompilierungszeit sind einfach ein äußerst ausgeklügeltes und minimales konkretes Beispiel für die Funktionen, die uns derzeit zur Verfügung stehen.
Steven Lu

Antworten:

7

Ja, Virginia, da ist ein Weihnachtsmann.

Die Idee, Programme zum Ändern von Programmen zu verwenden, gibt es schon lange. Die ursprüngliche Idee kam von John von Neumann in Form von Computern mit gespeicherten Programmen. Maschinencode Das Ändern von Maschinencode auf willkürliche Weise ist jedoch ziemlich unpraktisch.

Die Leute wollen im Allgemeinen den Quellcode ändern . Dies wird meist in Form von Programmtransformationssystemen (PTS) realisiert .

PTS bietet im Allgemeinen für mindestens eine Programmiersprache die Möglichkeit, ASTs zu analysieren, diesen AST zu manipulieren und gültigen Quelltext neu zu generieren. Wenn Sie sich tatsächlich in den meisten gängigen Sprachen umsehen, hat jemand ein solches Tool (Clang ist ein Beispiel für C ++, der Java-Compiler bietet diese Funktion als API, Microsoft bietet Rosyln, Eclipse's JDT, ...) mit einer Prozedur an API, die eigentlich ziemlich nützlich ist. Für die breitere Community kann fast jede sprachspezifische Community auf so etwas verweisen, das mit unterschiedlichen Reifegraden implementiert wird (normalerweise bescheiden, viele "nur Parser, die ASTs produzieren"). Viel Spaß beim Metaprogrammieren.

[Es gibt eine reflexionsorientierte Community, die versucht, Metaprogrammierung innerhalb der Programmiersprache durchzuführen, jedoch nur eine Verhaltensänderung zur Laufzeit erreicht und nur in dem Maße, in dem die Sprachcompiler einige Informationen durch Reflektion verfügbar gemacht haben. Mit Ausnahme von LISP gibt es immer Details zu dem Programm, die nicht durch Reflexion verfügbar sind ("Luke, du brauchst die Quelle"), die immer die Möglichkeiten der Reflexion einschränken.]

Die interessanteren PTS tun dies für beliebige Sprachen (Sie geben dem Tool eine Sprachbeschreibung als Konfigurationsparameter, einschließlich mindestens der BNF). Mit einem solchen PTS können Sie auch eine "Quelle-zu-Quelle" -Transformation durchführen, z. B. Muster direkt mithilfe der Oberflächensyntax der Zielsprache angeben . Mit solchen Mustern können Sie interessierende Fragmente codieren und / oder Codefragmente suchen und ersetzen. Dies ist weitaus praktischer als die Programmier-API, da Sie nicht alle mikroskopischen Details der ASTs kennen müssen, um den größten Teil Ihrer Arbeit zu erledigen. Stellen Sie sich dies als Meta-Metaprogrammierung vor: -}

Ein Nachteil: Wenn das PTS nicht verschiedene Arten nützlicher statischer Analysen bietet (Symboltabellen, Steuerungs- und Datenflussanalysen), ist es schwierig, auf diese Weise wirklich interessante Transformationen zu schreiben, da Sie für die meisten praktischen Aufgaben Typen überprüfen und den Informationsfluss überprüfen müssen. Leider ist diese Fähigkeit in der allgemeinen PTS tatsächlich selten. (Es ist immer nicht verfügbar mit dem immer vorgeschlagenen "Wenn ich nur einen Parser hätte ...". Weitere Informationen zu "Life After Parsing" finden Sie in meiner Biografie.)

Es gibt einen Satz, der besagt, dass Sie eine beliebige Transformation durchführen können, wenn Sie das Umschreiben von Zeichenfolgen (also das Umschreiben von Bäumen) durchführen können. und daher stützen sich einige PTS darauf, um zu behaupten, dass Sie alles mit nur den von ihnen angebotenen Baumumschreibungen metrogrammieren können. Während der Satz in dem Sinne zufriedenstellend ist, dass Sie jetzt sicher sind, dass Sie alles tun können, ist er ebenso unbefriedigend wie die Fähigkeit einer Turingmaschine, etwas zu tun, die Programmierung einer Turingmaschine nicht zur Methode der Wahl macht. (Dasselbe gilt für Systeme mit nur prozeduralen APIs, wenn Sie damit willkürliche Änderungen am AST vornehmen können [und tatsächlich denke ich, dass dies nicht für Clang gilt]).

Was Sie wollen, ist das Beste aus beiden Welten, ein System, das Ihnen die Allgemeingültigkeit des sprachparametrisierten PTS-Typs (auch für die Verarbeitung mehrerer Sprachen) bietet, mit zusätzlichen statischen Analysen und der Möglichkeit, Transformationen von Quelle zu Quelle mit prozeduralen zu mischen APIs. Ich kenne nur zwei , die dies tun:

  • Rascal (MPL) MetaProgramming Language
  • unser DMS Software Reengineering Toolkit

Wenn Sie die Sprachbeschreibungen und statischen Analysatoren nicht selbst schreiben möchten (für C ++ ist dies ein enormer Arbeitsaufwand, weshalb Clang sowohl als Compiler als auch als allgemeine prozedurale Metaprogrammiergrundlage konstruiert wurde), benötigen Sie ein PTS mit ausgereiften Sprachbeschreibungen schon verfügbar. Andernfalls verbringen Sie Ihre ganze Zeit damit, das PTS zu konfigurieren, und keiner erledigt die Arbeit, die Sie eigentlich tun wollten. [Wenn Sie eine zufällige Nicht-Mainstream-Sprache auswählen, ist dieser Schritt sehr schwer zu vermeiden].

Rascal versucht dies, indem er "OPP" (Other People's Parsers) kooptiert, aber das hilft nicht bei der statischen Analyse. Ich denke, sie haben Java ziemlich gut in der Hand, aber ich bin mir sehr sicher, dass sie kein C oder C ++ machen. Aber es ist ein akademisches Forschungswerkzeug; schwer zu beschuldigen.

Ich betone, dass für unser [kommerzielles] DMS-Tool Java, C, C ++ vollständige Frontends verfügbar sind. Für C ++ deckt es fast alles in C ++ 14 für GCC und sogar die Variationen von Microsoft (und wir polieren jetzt), die Makroerweiterung und das bedingte Management sowie die Steuerung und Datenflussanalyse auf Methodenebene ab. Und ja, Sie können Grammatikänderungen auf praktische Weise festlegen. Wir haben ein benutzerdefiniertes VectorC ++ - System für einen Client erstellt, das C ++ radikal erweitert hat, um die Anzahl der datenparallelen F90 / APL-Array-Operationen zu verwenden. DMS wurde verwendet, um andere massive Metaprogrammierungsaufgaben auf großen C ++ - Systemen auszuführen (z. B. Umgestaltung der Anwendungsarchitektur). (Ich bin der Architekt hinter DMS).

Viel Spaß beim Meta-Metaprogrammieren.

Ira Baxter
quelle
Cool, ich denke, Clang und DMS haben zwar einige überlappende Funktionen, sind aber Software-Teile, die nicht wirklich zur selben Kategorie gehören. Ich meine, einer ist wahrscheinlich lächerlich teuer und ich könnte wahrscheinlich niemals die Ressourcen rechtfertigen, die nötig wären, um Zugang dazu zu erhalten, und der andere ist uneingeschränkt kostenlos Open Source. Dies ist ein großer Unterschied ... Ein Teil dessen, was mich an diesen aufregenden Metaprogrammierfunktionen begeistert, ist in der Tat die Tatsache, dass es nicht nur zulässig ist, sie frei zu verwenden, sondern auch klangbasierte Binärwerkzeuge frei zu verteilen.
Steven Lu
Alles, was kommerziell verkauft wird, ist im Vergleich zu kostenlos "unglaublich teuer". Rohkosten sind nicht das Problem; Was zählt, ist, dass für einige Leute die Amortisation für den Erwerb des kommerziellen Produkts höher ist als die Amortisation für das kostenlose Artefakt, sonst würde es keine kommerzielle Software geben. Dies hängt natürlich von Ihren spezifischen Bedürfnissen ab. Clang ist ein interessanter Punkt im Werkzeugbereich und wird sicherlich nützliche Anwendungspunkte haben. Ich würde gerne denken (da ich der DMS-Architekt bin), dass DMS über umfassendere Funktionen verfügt. Es ist unwahrscheinlich, dass Clang andere Sprachen als C ++ gut unterstützt.
Ira Baxter
Bestimmt. Es steht außer Frage, dass DMS unglaublich mächtig ist, fast bis zur Magie (à la Arthur C. Clarke), und obwohl Clang großartig ist, ist es wirklich nur ein gut geschriebenes C ++ - Frontend, von dem es viele gibt. Sehr viele kleine Schritte vorwärts, das heißt, es wäre immer noch nicht wirklich fair, es mit DMS zu vergleichen. Leider schreibt sich funktionierende Software selbst mit solch leistungsstarken Tools nicht von selbst. Es muss noch durch sorgfältige Übersetzung mit den Werkzeugen oder (so ziemlich immer die überlegene Option) frisch geschrieben entstehen.
Steven Lu
Sie können es sich nicht leisten, Tools wie Clang oder DMS aus Fresh zu erstellen. Normalerweise können Sie es sich auch nicht leisten, die von Ihnen geschriebene Bewerbung mit einem 10-köpfigen Team über 5 Jahre zu werfen. Wir werden solche Tools immer häufiger benötigen, da die Größe und Lebensdauer der Software weiter zunimmt.
Ira Baxter
@StevenLu: Nun, DMS dankt Ihnen für das Kompliment, aber es ist nichts Magisches daran. DMS hat den Vorteil von fast 2 linearen Jahrzehnten Engineering und einer sauberen Architekturplattform (aw, shucks, YMMV), die sich ziemlich gut behauptet hat. Ebenso hat Clang eine Menge guter Technik. Ich stimme zu, sie wurden nicht entwickelt, um genau das gleiche Problem zu lösen ... Der Umfang von DMS soll ausdrücklich größer sein, wenn es um symbolische Programmmanipulation geht, und viel kleiner, wenn es darum geht, ein Produktionscompiler zu sein.
Ira Baxter
4

Die Metaprogrammierung in C ++ mit der Compiler-API (anstelle von Vorlagen) ist in der Tat interessant und praktisch möglich. Da die Metaprogrammierung (noch) nicht standardisiert ist, sind Sie an einen bestimmten Compiler gebunden, was bei Vorlagen nicht der Fall ist.

Ich frage mich also, warum das nicht schon alle machen. Ist es so, dass diese Funktionalität von clang so neu ist und niemand mit der riesigen Klassenhierarchie von clangs AST vertraut ist? Das kann es nicht sein.

Viele Leute tun dies, aber in anderen Sprachen. Meiner Meinung nach sehen die meisten C ++ - (oder Java- oder C-) Entwickler die Notwendigkeit dafür (möglicherweise zu Recht) nicht oder sind nicht an Metaprogrammierungsansätze gewöhnt. Ich denke auch, dass sie mit den Refactoring- / Code-Generierungsfunktionen ihrer IDE zufrieden sind und dass alles, was schicker ist, als zu komplex / schwer zu warten / schwer zu debuggen angesehen werden kann. Ohne geeignete Werkzeuge könnte es wahr sein. Sie sollten auch Trägheit und andere nichttechnische Probleme berücksichtigen, z. B. die Einstellung und / oder Schulung von Mitarbeitern.

Übrigens, da wir Common Lisp und sein Makrosystem erwähnen (siehe Basiles Antwort), muss ich sagen, dass Clasp erst gestern veröffentlicht wurde (ich bin nicht angeschlossen):

Clasp soll eine konforme Common Lisp-Implementierung sein, die mit LLVM IR kompiliert wird. Darüber hinaus werden Clang-Bibliotheken (AST, Matcher) für den Entwickler verfügbar gemacht.

  • Erstens bedeutet dies, dass Sie in CL schreiben und C ++ nicht mehr verwenden können, außer wenn Sie die Bibliotheken verwenden (und wenn Sie Makros benötigen, verwenden Sie CL-Makros).

  • Zweitens können Sie in CL Tools für Ihren vorhandenen C ++ - Code schreiben (Analyse, Refactoring, ...).

Core-Dump
quelle
3

Einige C ++ - Compiler verfügen über eine mehr oder weniger dokumentierte und stabile API, insbesondere die meisten Compiler für freie Software.

Clang / LLVM besteht hauptsächlich aus einer großen Anzahl von Bibliotheken, die Sie verwenden können.

Aktuelle GCC akzeptiert Plugins . Insbesondere können Sie es mit MELT erweitern (das selbst ein Meta-Plugin ist und Ihnen eine übergeordnete domänenspezifische Sprache zur Erweiterung von GCC zur Verfügung stellt).

Beachten Sie, dass die Syntax von C ++ in GCC (und wahrscheinlich auch nicht in Clang) nicht einfach erweiterbar ist. Sie können jedoch eigene Pragmas, integrierte Funktionen, Attribute und Compiler-Durchläufe hinzufügen, um das zu tun, was Sie möchten (möglicherweise auch einige Präprozessor-Makros bereitstellen, die diese Dinge aufrufen eine benutzerfreundliche Syntax zu geben).

Sie könnten an mehrstufigen Sprachen und Compilern interessiert sein, siehe z. B. das Implementieren mehrstufiger Sprachen mit ASTs, Gensym und Reflection von C.Calcagno et al. und arbeite um MetaOcaml herum . Sie sollten sich unbedingt die Makrofunktionen von Common Lisp ansehen . Und Sie können durch interessieren JIT - Bibliotheken wie libjit , GNU Blitz , auch LLVM oder einfach -bei Laufzeit - einig C ++ Code generieren, Gabel eine Zusammenstellung von in eine gemeinsam genutztes Objekt dynamischen Bibliothek, dann dlopen (3) , dass die gemeinsamen Objekt. Der Blog von J.Pitrat bezieht sich auch auf solche reflektierenden Ansätze. Und auch RefPerSys .

Basile Starynkevitch
quelle
Interessant. Es ist sehr gut zu sehen, dass sich GCC hier weiterentwickelt. Dies ist keine Antwort, die irgendetwas anspricht, was ich gefragt habe, aber ich mag es trotzdem.
Steven Lu
Betreff: Ihre neuen Änderungen ... Das ist ein guter Punkt beim Umschreiben von Code. Dies führt tatsächlich dazu, dass solche Metaprogrammfunktionen auch in C ++ verfügbar sind, viel zugänglicher als zuvor, was ebenfalls sehr interessant ist.
Steven Lu