C ++: métaprogrammation avec une API de compilation plutôt qu'avec des fonctionnalités C ++

10

Cela a commencé comme une question SO mais j'ai réalisé que c'était assez peu conventionnel et basé sur la description réelle sur les sites Web, il pourrait être mieux adapté aux programmeurs.se puisque la question a beaucoup de poids conceptuel.

J'ai appris le clang LibTooling et c'est un outil très puissant capable d'exposer l'intégralité du "nitty gritty" du code de manière conviviale, c'est-à-dire de manière sémantique , et non en devinant non plus. Si clang peut compiler votre code, alors clang est certain de la sémantique de chaque caractère à l'intérieur de ce code.

Permettez-moi maintenant de prendre un peu de recul.

Il existe de nombreux problèmes pratiques qui se posent lorsque l'on s'engage dans la métaprogrammation de modèles C ++ (et en particulier lorsque l'on s'aventure au-delà des modèles sur le territoire de macros intelligentes mais terrifiantes). Pour être honnête, pour de nombreux programmeurs, y compris moi-même, de nombreuses utilisations ordinaires des modèles sont également quelque peu terrifiantes.

Je suppose qu'un bon exemple serait les chaînes de compilation . C'est une question qui remonte à plus d'un an maintenant, mais il est clair que le C ++ à l'heure actuelle ne facilite pas la tâche des simples mortels. Bien que regarder ces options ne soit pas suffisant pour me causer des nausées, cela me laisse néanmoins confiant quant à la capacité de produire un code machine magique et efficace au maximum pour s'adapter à toutes les applications fantaisistes que j'ai pour mon logiciel.

Je veux dire, avouons-le, les gens, les chaînes sont assez simples et basiques. Certains d'entre nous veulent juste un moyen pratique d'émettre du code machine qui a certaines chaînes "intégrées" beaucoup plus que ce que nous obtenons en le codant de manière simple. Dans notre code C ++.

Entrez clang et LibTooling, qui expose l'arbre de syntaxe abstraite (AST) du code source et permet à une application C ++ personnalisée simple de manipuler correctement et de manière fiable le code source brut (à l'aide Rewriter) aux côtés d'un riche modèle sémantique orienté objet de tout dans l'AST. Il gère beaucoup de choses. Il connaît les extensions de macro et vous permet de suivre ces chaînes. Oui, je parle de transformation ou de traduction de code source à source.

Ma thèse fondamentale ici est que clang nous permet maintenant de créer des exécutables qui eux-mêmes peuvent fonctionner comme les étapes de préprocesseur personnalisées idéales pour notre logiciel C ++, et nous pouvons implémenter ces étapes de métaprogrammation avec C ++. Nous sommes simplement contraints par le fait que cette étape doit prendre en entrée du code C ++ valide et produire en sortie du code C ++ plus valide. De plus, quelles que soient les autres contraintes auxquelles votre système de construction s'applique.

L'entrée doit être au moins très proche du code C ++ valide car, après tout, clang est le frontal du compilateur et nous ne faisons que fouiner et être créatifs avec son API. Je ne sais pas s'il est possible de définir une nouvelle syntaxe à utiliser, mais il est clair que nous devons développer les moyens de l'analyser correctement et de l'ajouter au projet clang pour ce faire. Attendre plus, c'est avoir quelque chose dans le projet Clang qui est hors de portée.

Pas de problème. J'imagine que certaines fonctions macro sans opération peuvent gérer cette tâche.

Une autre façon de voir ce que je décris est d'implémenter des constructions de métaprogrammation à l'aide du runtime C ++ en manipulant l'AST de notre code source (grâce à clang et son API) au lieu de les implémenter en utilisant les outils plus limités disponibles dans le langage lui-même. Cela présente également des avantages clairs en termes de performances de compilation (les en-têtes lourds en modèles ralentissent la compilation proportionnellement à la fréquence à laquelle vous les utilisez. Beaucoup de choses compilées sont ensuite soigneusement adaptées et jetées par l'éditeur de liens).

Cela, cependant, se fait au prix de l'introduction d'une ou deux étapes supplémentaires dans le processus de construction et également de la nécessité d'écrire des logiciels (certes) un peu plus verbeux (mais au moins c'est du runtime C ++ simple) dans le cadre de notre outil. .

Ce n'est pas tout. Je suis à peu près certain qu'il existe un espace beaucoup plus grand de fonctionnalités qui peut être obtenu en générant du code qui est extrêmement difficile ou impossible avec les fonctionnalités du langage de base. En C ++, vous pouvez écrire un modèle ou une macro ou une combinaison folle des deux, mais dans un outil de clang, vous pouvez modifier les classes et les fonctions de TOUTE manière que vous pouvez réaliser avec C ++, au moment de l' exécution , tout en ayant un accès complet au contenu sémantique, en plus du modèle et des macros et tout le reste.

Je me demande donc pourquoi tout le monde ne le fait pas déjà. Est-ce que cette fonctionnalité de clang est si nouvelle et que personne ne connaît l'énorme hiérarchie des classes de l'AST de clang? Ça ne peut pas être ça.

Peut-être que je sous-estime un peu la difficulté de cela, mais faire une "manipulation de chaîne au moment de la compilation" avec un outil de clang est presque criminellement simple. C'est verbeux, mais c'est incroyablement simple. Tout ce qui est nécessaire est un tas de fonctions macro sans opération qui correspondent aux std::stringopérations réelles réelles . Le plugin clang implémente cela en récupérant tous les appels de macro sans opération pertinents et effectue les opérations avec des chaînes. Cet outil est ensuite inséré dans le cadre du processus de génération. Pendant la génération, ces appels de fonction macro sans opération sont automatiquement évalués dans leurs résultats, puis réinsérés en tant que vieilles chaînes de compilation simples dans le programme. Le programme peut ensuite être compilé comme d'habitude. En fait, ce programme résultant est également beaucoup plus portable, ne nécessitant pas de nouveau compilateur sophistiqué prenant en charge C ++ 11.

Steven Lu
la source
C'est une question inhabituellement longue. Pourriez-vous peut-être le résumer à vos points les plus pertinents?
amon
Je poste beaucoup de longues questions. Mais surtout avec celui-ci, toutes les parties de la question sont importantes, je pense. Peut-être sauter les 6 premiers paragraphes? Haha.
Steven Lu
3
Cela ressemble beaucoup aux macros syntaxiques lancées en Lisp et récemment reprises par Haxe, Nemerle, Scala et des langages similaires. Il y a beaucoup de lecture sur les raisons pour lesquelles les macros Lisp sont considérées comme nuisibles. Bien que je n'aie pas encore entendu d'argument convaincant, vous trouverez peut-être des raisons pour lesquelles les gens hésitent à les ajouter à toutes les langues (en plus du fait que ce n'est pas nécessairement simple).
back2dos
Ouais, son méta-ifying C ++. Ce qui peut signifier un code meilleur et plus rapide. Quant à ces langues. Eh bien, par où commencer? Qu'est-ce qu'un jeu vidéo de plusieurs millions de dollars implémenté dans l'une de ces langues? Qu'est-ce qu'un navigateur Web moderne implémenté dans l'une de ces langues? Noyau OS? D'accord, il semble que Haxe ait une certaine traction, mais vous avez compris.
Steven Lu
1
@nwp, Eh bien, je ne peux pas m'empêcher de souligner que vous semblez avoir raté l'intégralité du message. Les chaînes de compilation sont simplement un exemple concret le plus artificiel et le plus minimal des capacités dont nous disposons maintenant.
Steven Lu

Réponses:

7

Oui, Virginie, il y a un Père Noël.

L'idée d'utiliser des programmes pour modifier des programmes existe depuis longtemps. L'idée originale est venue de John von Neumann sous la forme d'ordinateurs à programme stocké. Mais le code machine qui modifie le code machine de manière arbitraire est assez peu pratique.

Les gens veulent généralement modifier le code source . Ceci est principalement réalisé sous la forme de systèmes de transformation de programme (PTS) .

PTS offre généralement, pour au moins un langage de programmation, la possibilité d'analyser les AST, de manipuler cet AST et de régénérer le texte source valide. Si en fait vous creusez, pour la plupart des langages traditionnels, quelqu'un a construit un tel outil (Clang est un exemple pour C ++, le compilateur Java offre cette capacité en tant qu'API, Microsoft propose Rosyln, le JDT d'Eclipse, ...) avec une procédure API qui est en fait assez utile. Pour la communauté plus large, presque toutes les communautés spécifiques à une langue peuvent indiquer quelque chose comme ça, implémenté avec différents niveaux de maturité (généralement modestes, de nombreux "juste des analyseurs produisant des AST"). Bonne métaprogrammation.

[Il y a une communauté orientée vers la réflexion qui essaie de faire de la métaprogrammation à l' intérieur du langage de programmation, mais qui n'atteint que la modification du comportement "d'exécution", et uniquement dans la mesure où les compilateurs de langage ont rendu certaines informations disponibles par réflexion. À l'exception de LISP, il y a toujours des détails sur le programme qui ne sont pas disponibles par réflexion ("Luke, vous avez besoin de la source") qui limitent toujours ce que la réflexion peut faire.]

Les PTS les plus intéressants le font pour les langues arbitraires (vous donnez à l'outil une description de langue comme paramètre de configuration, y compris au moins le BNF). Ces PTS vous permettent également de faire une transformation "source à source", par exemple, de spécifier des modèles directement en utilisant la syntaxe de surface du langage ciblé; en utilisant de tels modèles, vous pouvez coder des fragments d'intérêt et / ou trouver et remplacer des fragments de code. C'est beaucoup plus pratique que l'API de programmation, car vous n'avez pas besoin de connaître tous les détails microscopiques des AST pour effectuer la plupart de votre travail. Considérez cela comme une méta-métaprogrammation: -}

Un inconvénient: à moins que le PTS ne propose différents types d'analyses statiques utiles (tables de symboles, analyses de contrôle et de flux de données), il est difficile d'écrire des transformations vraiment intéressantes de cette façon, car vous devez vérifier les types et vérifier les flux d'informations pour la plupart des tâches pratiques. Malheureusement, cette capacité est en fait rare dans le PTS général. (Il est toujours indisponible avec le jamais proposé "Si je viens d'avoir un analyseur ..." Voir ma biographie pour une discussion plus longue de "Life After Parsing").

Il y a un théorème qui dit que si vous pouvez faire une réécriture de chaîne [donc une réécriture d'arbre] vous pouvez faire une transformation arbitraire; et donc un certain nombre de PTS s'appuient sur cela pour prétendre que vous pouvez métaprogrammer n'importe quoi avec juste les réécritures d'arbres qu'ils offrent. Bien que le théorème soit satisfaisant dans le sens où vous êtes maintenant sûr de pouvoir tout faire, il n'est pas satisfaisant de la même manière que la capacité d'une machine de Turing à faire quoi que ce soit ne fait pas de la programmation d'une machine de Turing la méthode de choix. (Il en va de même pour les systèmes dotés uniquement d'API procédurales, s'ils vous permettent d'apporter des modifications arbitraires à l'AST [et en fait, je pense que ce n'est pas le cas de Clang]).

Ce que vous voulez, c'est le meilleur des deux mondes, un système qui vous offre la généralité du type de PTS paramétré par la langue (même en gérant plusieurs langues), avec les analyses statiques supplémentaires, la possibilité de mélanger les transformations de source à source avec les procédures Apis. Je n'en connais que deux :

  • Langage de méta-programmation Rascal (MPL)
  • notre boîte à outils de réingénierie logicielle DMS

À moins que vous ne vouliez écrire les descriptions de langage et les analyseurs statiques vous-même (pour C ++, c'est une énorme quantité de travail, c'est pourquoi Clang a été construit à la fois comme compilateur et comme base générale de métaprogrammation procédurale), vous voudrez un PTS avec des descriptions de langage matures Déjà disponible. Sinon, vous passerez tout votre temps à configurer le PTS, et aucun ne fera le travail que vous vouliez réellement faire. [Si vous choisissez une langue aléatoire non courante, cette étape est très difficile à éviter].

Rascal essaie de le faire en cooptant "OPP" (autres analyseurs populaires) mais cela n'aide pas avec la partie d'analyse statique. Je pense qu'ils ont assez bien Java en main, mais je suis très sûr qu'ils ne font pas C ou C ++. Mais, c'est un outil de recherche universitaire; difficile de les blâmer.

Je souligne, notre outil DMS [commercial] a des frontaux complets Java, C, C ++ disponibles. Pour C ++, il couvre presque tout en C ++ 14 pour GCC et même les variations de Microsoft (et nous peaufinons maintenant), l'expansion des macros et la gestion conditionnelle, et le contrôle au niveau de la méthode et l'analyse du flux de données. Et oui, vous pouvez spécifier les changements de grammaire de manière pratique; nous avons construit un système VectorC ++ personnalisé pour un client qui a radicalement étendu C ++ pour utiliser ce qui équivaut à des opérations de tableaux de données parallèles F90 / APL. DMS a été utilisé pour effectuer d'autres tâches de métaprogrammation massives sur de grands systèmes C ++ (par exemple, le remodelage architectural d'une application). (Je suis l'architecte derrière DMS).

Bonne méta-métaprogrammation.

Ira Baxter
la source
Cool, je pense que Clang et DMS, bien qu'ils aient des capacités qui se chevauchent, sont des logiciels qui ne sont pas vraiment dans la même catégorie. Je veux dire, l'un est probablement ridiculement cher et je ne pourrais probablement jamais justifier les ressources qu'il faudrait pour y accéder, et l'autre est open source gratuit et sans restriction. C'est une énorme différence ... Une partie de ce qui m'excite au sujet de ces capacités de métaprogrammation passionnantes est en effet le fait qu'il est permis non seulement de l'utiliser librement, mais aussi de distribuer librement des outils binaires basés sur Clang.
Steven Lu
Tout ce qui est vendu dans le commerce est "extrêmement cher" par rapport à gratuit. Le coût brut n'est pas le problème; ce qui importe, c'est que pour certaines personnes, le retour sur investissement pour l'acquisition du produit commercial est plus élevé que le retour sur investissement pour l'artefact gratuit, sinon il n'y aurait pas de logiciel commercial. Cela dépend évidemment de vos besoins spécifiques. Clang est un point intéressant dans l'espace des outils et aura certainement des points d'application utiles. J'aimerais penser (puisque je suis l'architecte DMS) que DMS a des capacités plus larges. Il est peu probable que Clang prenne bien en charge des langages autres que C ++, par exemple.
Ira Baxter
Certainement. Il ne fait aucun doute que le DMS est incroyablement puissant, presque au point de la magie (à la Arthur C. Clarke), et bien que le clang soit génial, ce n'est vraiment qu'un frontend C ++ bien écrit, dont il existe de nombreux. Un grand nombre de petits pas en avant, que ce soit, il ne serait toujours pas vraiment juste de le comparer au DMS. Hélas, même avec des outils aussi puissants à notre disposition, un logiciel qui fonctionne ne s'écrit pas lui-même. Il doit encore exister grâce à une traduction soignée à l'aide des outils ou (à peu près toujours l'option supérieure) écrite à nouveau.
Steven Lu
Vous ne pouvez pas vous permettre de créer des outils comme Clang ou DMS à partir de frais. Vous ne pouvez généralement pas vous permettre de lancer l'application que vous avez écrite avec une équipe de 10 personnes sur 5 ans. Nous aurons de plus en plus besoin de tels outils, à mesure que la taille et la durée de vie des logiciels continuent de croître.
Ira Baxter
@StevenLu: Eh bien, DMS vous remercie pour le compliment, mais il n'y a rien de magique à ce sujet. DMS a l'avantage de près de 2 décennies d'ingénierie linéaire et d'une plate-forme architecturale propre (aw, shucks, YMMV) qui a assez bien résisté. De même, Clang a beaucoup de bonne ingénierie. Je suis d'accord, ils n'ont pas été conçus pour résoudre exactement le même problème ... La portée de DMS est explicitement destinée à être plus grande lorsqu'il s'agit de manipulation de programme symbolique, et beaucoup plus petite lorsqu'il s'agit d'être un compilateur de production.
Ira Baxter
4

La métaprogrammation en C ++ avec l'API des compilateurs (au lieu d'utiliser des modèles) est en effet intéressante et pratiquement possible. La métaprogrammation n'étant pas (encore) standardisée, vous serez lié à un compilateur spécifique, ce qui n'est pas le cas avec les modèles.

Je me demande donc pourquoi tout le monde ne le fait pas déjà. Est-ce que cette fonctionnalité de clang est si nouvelle et que personne ne connaît l'énorme hiérarchie des classes de l'AST de clang? Ça ne peut pas être ça.

Beaucoup de gens le font, mais dans d'autres langues. Mon opinion est que la plupart des développeurs C ++ (ou Java ou C) n'en voient pas la nécessité (peut-être à juste titre), ou ne sont pas habitués aux approches de métaprogrammation; Je pense également qu'ils sont satisfaits des fonctionnalités de refactorisation / génération de code de leur IDE, et que tout ce qui est plus sophistiqué pourrait être considéré comme trop complexe / difficile à maintenir / difficile à déboguer. Sans outils appropriés, cela pourrait être vrai. Vous devez également prendre en compte l'inertie et d'autres problèmes non techniques, comme l'embauche et / ou la formation de personnes.

Soit dit en passant, puisque nous mentionnons Common Lisp et son système de macros (voir la réponse de Basile), je dois dire qu'hier, Clasp a été libéré (je ne suis pas affilié):

Clasp a l' intention d'être une implémentation Common Lisp conforme qui se compile en LLVM IR. De plus, il expose les bibliothèques Clang (AST, Matcher) au développeur.

  • Tout d'abord, cela signifie que vous pouvez écrire en CL et ne plus utiliser C ++, sauf lorsque vous utilisez ses bibliothèques (et si vous avez besoin de macros, utilisez les macros CL).

  • Deuxièmement, vous pouvez écrire des outils en CL pour votre code C ++ existant (analyse, refactoring, ...).

coredump
la source
3

Plusieurs compilateurs C ++ ont des API plus ou moins documentées et stables, en particulier la plupart des compilateurs de logiciels libres.

Clang / LLVM est principalement un grand ensemble de bibliothèques, et vous pouvez les utiliser.

GCC récent accepte les plugins . En particulier, vous pouvez l'étendre en utilisant MELT (qui est lui-même un méta-plugin, vous fournissant un langage spécifique à un domaine de niveau supérieur pour étendre GCC).

Notez que la syntaxe de C ++ n'est pas facilement extensible dans GCC (et probablement ni dans Clang), mais vous pouvez ajouter vos propres pragmas, buildins, attributs et passes de compilateur pour faire ce que vous voulez (peut-être aussi fournir des macros de préprocesseur appelant ces choses pour donner une syntaxe conviviale).

Vous pourriez être intéressé par les langages à plusieurs étapes et les compilateurs, voir par exemple le document Implémentation de langages à plusieurs étapes utilisant les AST, Gensym et le document de réflexion de C.Calcagno et al. et contournez MetaOcaml . Vous devriez certainement regarder à l'intérieur des fonctions macro de Common Lisp . Et vous pourriez être intéressé par les bibliothèques JIT comme libjit , GNU lightning , même LLVM , ou simplement - au moment de l'exécution! - générer du code C ++, en générer une compilation dans une bibliothèque dynamique d'objets partagés, puis dlopen (3) qui a partagé objet. Le blog de J.Pitrat est également lié à de telles approches réflexives. Et aussi RefPerSys .

Basile Starynkevitch
la source
Intéressant. C'est très bien de voir GCC continuer d'évoluer ici. Ce n'est pas une réponse qui répond à tout ce que j'ai demandé, mais je l'aime quand même.
Steven Lu
Re: vos nouvelles modifications ... C'est un bon point sur la réécriture du code lui-même. En fait, cela commence à apporter une telle capacité de méta-programme au C ++, beaucoup plus accessible qu'auparavant, ce qui est également très intéressant.
Steven Lu