C ++: metaprogrammazione con un'API del compilatore anziché con le funzionalità C ++


10

Questo è iniziato come una domanda SO ma mi sono reso conto che è piuttosto non convenzionale e basato sulla descrizione attuale sui siti Web, potrebbe essere più adatto ai programmatori. Visto che la domanda ha molto peso concettuale.

Ho imparato il clanging LibTooling ed è uno strumento molto potente in grado di esporre l'intero "grintoso" del codice in un modo amichevole, cioè in modo semantico , e non indovinando neanche. Se clang può compilare il tuo codice, allora clang è certo della semantica di ogni singolo carattere all'interno di quel codice.

Ora permettimi di fare un passo indietro per un momento.

Ci sono molti problemi pratici che sorgono quando ci si impegna nella metaprogrammazione dei template C ++ (e specialmente quando si avventurano oltre i template nel territorio di macro intelligenti sebbene terrificanti). Ad essere sincero, per molti programmatori, me compreso, anche molti degli usi ordinari dei template sono in qualche modo terrificanti.

Immagino che un buon esempio sarebbe stringhe in fase di compilazione . Questa è una domanda che ha più di un anno ormai, ma è chiaro che il C ++ in questo momento non lo rende facile per i semplici mortali. Mentre guardare queste opzioni non è abbastanza per indurre la nausea per me, mi lascia comunque incerto sul fatto di essere in grado di produrre un codice macchina magico e massimamente efficiente per adattarsi a qualsiasi fantastica applicazione che ho per il mio software.

Voglio dire, ammettiamolo, gente, le stringhe sono piuttosto semplici ed elementari. Alcuni di noi vogliono solo un modo conveniente per emettere codice macchina che ha "stringhe" di determinate stringhe molto più di quanto otteniamo quando lo codifichiamo in modo semplice. Nel nostro codice C ++.

Immettere clang e LibTooling, che espone l'albero di sintassi astratto (AST) del codice sorgente e consente a una semplice applicazione C ++ personalizzata di manipolare correttamente e in modo affidabile il codice sorgente grezzo (usando Rewriter) insieme a un ricco modello semantico orientato agli oggetti di tutto ciò che si trova nell'AST. Gestisce molte cose. Conosce le macro espansioni e ti consente di seguire quelle catene. Sì, sto parlando della trasformazione o della traduzione del codice sorgente-sorgente.

La mia tesi fondamentale qui è che clang ora ci consente di creare file eseguibili che possono funzionare da soli come stadi del preprocessore personalizzato ideale per il nostro software C ++ e che possiamo implementare queste fasi di metaprogrammazione con C ++. Siamo semplicemente limitati dal fatto che questa fase deve prendere input che è un codice C ++ valido e produrre come output un codice C ++ più valido. Inoltre qualunque altra limitazione si applichi al tuo sistema di compilazione.

L'input deve essere almeno molto vicino al codice C ++ valido perché, dopo tutto, clang è il front-end del compilatore e stiamo solo cercando e siamo creativi con la sua API. Non so se ci siano disposizioni per poter definire una nuova sintassi da usare, ma chiaramente dobbiamo sviluppare i modi per analizzarla correttamente e aggiungerla al progetto clang per farlo. Aspettarsi di più significa avere qualcosa nel progetto clang che è fuori portata.

Non è un problema. Immagino che alcune funzioni macro no-op possano gestire questo compito.

Un altro modo di vedere quello che sto descrivendo è implementare costrutti di metaprogrammazione usando il runtime C ++ manipolando l'AST del nostro codice sorgente (grazie a clang e alla sua API) invece di implementarli usando gli strumenti più limitati disponibili nel linguaggio stesso. Ciò ha anche chiari vantaggi in termini di prestazioni della compilazione (le intestazioni pesanti del modello rallentano la compilazione in proporzione alla frequenza con cui li usi. Molte cose compilate vengono quindi accuratamente abbinate e gettate via dal linker).

Questo, tuttavia, comporta il costo di introdurre uno o due passaggi aggiuntivi nel processo di compilazione e anche la necessità di scrivere un software (certo) un po 'più dettagliato (ma almeno è un runtime semplice C ++) come parte del nostro strumento .

Non è l'intero quadro. Sono abbastanza certo che esiste uno spazio molto più ampio di funzionalità che si può ottenere dalla generazione di codice che è estremamente difficile o impossibile con le funzionalità del linguaggio di base. In C ++ puoi scrivere un modello o una macro o una combinazione folle di entrambi, ma in uno strumento clang puoi modificare classi e funzioni in QUALSIASI modo che puoi ottenere con C ++, in fase di esecuzione , pur avendo pieno accesso al contenuto semantico, oltre a template e macro e tutto il resto.

Quindi, mi chiedo perché tutti non lo stiano già facendo. È che questa funzionalità di clang è così nuova e nessuno ha familiarità con l'enorme gerarchia di classi dell'AST di clang? Non può essere.

Forse sto solo sottovalutando un po 'la difficoltà di questo, ma fare "manipolazione di stringhe in fase di compilazione" con uno strumento clang è quasi criminalmente semplice. È prolisso, ma è follemente semplice. Tutto ciò che serve sono un mucchio di funzioni macro no-op associate a std::stringoperazioni reali reali . Il plug-in clang lo implementa recuperando tutte le chiamate macro no-op rilevanti ed esegue le operazioni con stringhe. Questo strumento viene quindi inserito come parte del processo di compilazione. Durante la compilazione, queste chiamate a funzioni macro non operative vengono automaticamente valutate nei loro risultati e quindi reinserite come semplici stringhe di compilazione nel programma. Il programma può quindi essere compilato come al solito. In effetti, questo programma risultante è anche molto più portatile di conseguenza, non richiede un nuovo compilatore di fantasia che supporti C ++ 11.


Questa è una domanda insolitamente lunga. Potresti forse condensarlo nei punti più rilevanti?
amon,

Pubblico molte domande lunghe. Ma soprattutto con questo, tutte le parti della domanda sono importanti, penso. Forse salta i primi 6 paragrafi? Haha.
Steven Lu,

3
Sembra molto simile alle macro sintattiche introdotte per la prima volta in Lisp e recentemente raccolte da Haxe, Nemerle, Scala e lingue simili. C'è qualche lettura sul perché le macro di Lisp sono considerate dannose. Anche se non ho ancora sentito un argomento convincente, potresti trovare delle ragioni per cui le persone sono state riluttanti ad aggiungerle in ogni lingua (oltre al fatto che non è necessariamente semplice).
back2dos,

Sì, il suo C ++ meta-ifying. Il che può significare un codice migliore e più veloce. Per quanto riguarda quelle lingue. Bene, da dove comincio. Che cos'è un videogioco multimilionario implementato in una di quelle lingue? Che cos'è un browser Web moderno implementato in una di quelle lingue? Kernel del sistema operativo? Va bene, in realtà sembra che Haxe abbia una certa trazione, ma hai l'idea.
Steven Lu,

1
@nwp, Beh, non posso fare a meno di sottolineare che sembra che tu abbia perso l'intero punto del post. Le stringhe in fase di compilazione sono semplicemente un esempio concreto e minimale delle capacità disponibili per noi.
Steven Lu,

Risposte:


7

Sì, Virginia, c'è un Babbo Natale.

L'idea di utilizzare i programmi per modificare i programmi è in atto da molto tempo. L'idea originale venne da John von Neumann sotto forma di computer con programmi memorizzati. Ma modificare il codice macchina in modo arbitrario è abbastanza scomodo.

Le persone generalmente vogliono modificare il codice sorgente . Ciò si realizza principalmente sotto forma di sistemi di trasformazione del programma (PTS) .

PTS generalmente offre, per almeno un linguaggio di programmazione, la possibilità di analizzare gli AST, manipolarlo e rigenerare un testo sorgente valido. Se in effetti scavi in ​​giro, per la maggior parte dei linguaggi tradizionali, qualcuno ha creato un tale strumento (Clang è un esempio per C ++, il compilatore Java offre questa funzionalità come API, Microsoft offre Rosyln, JDT di Eclipse, ...) con un procedimento API effettivamente utile. Per la comunità più ampia, quasi ogni comunità specifica della lingua può indicare qualcosa di simile, implementato con vari livelli di maturità (di solito modesti, molti "solo parser che producono AST"). Buona metaprogrammazione.

[Esiste una comunità orientata alla riflessione che cerca di fare metaprogrammazione all'interno del linguaggio di programmazione, ma raggiunge solo la modifica del comportamento "runtime", e solo nella misura in cui i compilatori del linguaggio hanno reso disponibili alcune informazioni tramite la riflessione. Ad eccezione di LISP, ci sono sempre dettagli sul programma che non sono disponibili per reflection ("Luke, you need the source") che limitano sempre ciò che la riflessione può fare.]

Il PTS più interessante lo fa per le lingue arbitrarie (si fornisce allo strumento una descrizione della lingua come parametro di configurazione, includendo almeno il BNF). Tali PTS consentono anche di effettuare trasformazioni da "sorgente a sorgente", ad esempio, specificando direttamente i pattern utilizzando la sintassi di superficie del linguaggio di destinazione; utilizzando tali modelli, è possibile codificare frammenti di interesse e / o trovare e sostituire frammenti di codice. Questo è molto più conveniente dell'API di programmazione, perché non devi conoscere tutti i dettagli microscopici degli AST per svolgere la maggior parte del tuo lavoro. Pensa a questo come meta-metaprogrammazione: -}

Un aspetto negativo: a meno che il PTS non offra vari tipi di utili analisi statiche (tabelle dei simboli, analisi del controllo e del flusso di dati), è difficile scrivere trasformazioni davvero interessanti in questo modo, perché è necessario controllare i tipi e verificare i flussi di informazioni per la maggior parte delle attività pratiche. Sfortunatamente, questa funzionalità è in effetti rara nel PTS generale. (Non è sempre disponibile con la sempre proposta "Se avessi appena avuto un parser ..." Vedi la mia biografia per una discussione più lunga di "Life After Parsing").

C'è un teorema che dice che se puoi fare la riscrittura delle stringhe [quindi la riscrittura dell'albero] puoi fare una trasformazione arbitraria; e quindi un certo numero di PTS si basano su questo per affermare che è possibile metaprogrammare qualsiasi cosa con solo la riscrittura dell'albero che offrono. Mentre il teorema è soddisfacente nel senso che ora sei sicuro di poter fare qualsiasi cosa, non è soddisfacente nello stesso modo in cui la capacità di una macchina di Turing di fare qualsiasi cosa non rende la programmazione di una macchina di Turing il metodo di scelta. (Lo stesso vale per i sistemi con solo API procedurali, se ti consentiranno di apportare modifiche arbitrarie all'AST [e in effetti penso che questo non sia vero per Clang]).

Quello che vuoi è il meglio di entrambi i mondi, un sistema che ti offre la generalità del tipo di PTS parametrizzato al linguaggio (gestendo anche più lingue), con le analisi statiche aggiuntive, la capacità di mescolare trasformazioni da sorgente a sorgente con procedurale API. Ne conosco solo due che fanno questo:

  • Linguaggio di meta-programmazione Rascal (MPL)
  • il nostro toolkit di reengineering del software DMS

A meno che tu non voglia scrivere tu stesso le descrizioni del linguaggio e gli analizzatori statici (per C ++ questa è una quantità enorme di lavoro, motivo per cui Clang è stato costruito sia come compilatore che come base generale di metaprogrammazione procedurale), vorrai un PTS con descrizioni linguistiche mature già disponibile. Altrimenti trascorrerai tutto il tuo tempo a configurare il PTS e nessuno farà il lavoro che volevi veramente fare. [Se scegli un linguaggio casuale e non tradizionale, questo passaggio è molto difficile da evitare].

Rascal cerca di farlo cooptando "OPP" (Other People's Parser) ma ciò non aiuta con la parte dell'analisi statica. Penso che abbiano Java abbastanza bene in mano, ma sono molto sicuro che non facciano C o C ++. Ma è uno strumento di ricerca accademica; difficile incolparli.

Sottolineo che il nostro strumento DMS [commerciale] ha a disposizione front-end completi Java, C, C ++. Per C ++, copre quasi tutto in C ++ 14 per GCC e persino le variazioni di Microsoft (e stiamo lucidando ora), l'espansione delle macro e la gestione condizionale, il controllo a livello di metodo e l'analisi del flusso di dati. E sì, puoi specificare le modifiche grammaticali in modo pratico; abbiamo creato un sistema VectorC ++ personalizzato per un client che estendeva radicalmente C ++ per utilizzare la quantità di operazioni di array di dati paralleli F90 / APL. Il DMS è stato utilizzato per svolgere altri importanti compiti di metaprogrammazione su grandi sistemi C ++ (ad es. Rimodellamento dell'architettura dell'applicazione). (Sono l'architetto dietro DMS).

Felice meta-metaprogrammazione.


Fantastico, penso che Clang e DMS, sebbene abbiano alcune capacità sovrapposte, sono pezzi di software che non appartengono alla stessa categoria. Voglio dire, uno è probabilmente ridicolmente costoso e probabilmente non potrei mai giustificare le risorse che ci vorrebbe per accedervi, e l'altro è open source gratuito senza restrizioni. Questa è una differenza enorme ... Parte di ciò che mi entusiasma di queste entusiasmanti capacità di metaprogrammazione è in effetti il ​​fatto che è consentito non solo usarlo liberamente, ma anche distribuire liberamente strumenti binari basati su clang.
Steven Lu,

Tutto ciò che viene venduto commercialmente è "ridicolmente costoso" rispetto a quello gratuito. Il costo non è il problema; ciò che conta è che per alcune persone, il rimborso per l'acquisizione del prodotto commerciale è superiore al rimborso per l'artefatto gratuito, altrimenti non ci sarebbe software commerciale. Questo ovviamente dipende dalle tue esigenze specifiche. Clang è un punto interessante nello spazio degli strumenti e avrà sicuramente punti di utile applicazione. Mi piacerebbe pensare (dal momento che sono l'architetto DMS) che DMS ha capacità più ampie. È improbabile che Clang supporti bene anche lingue diverse dal C ++, ad esempio.
Ira Baxter,

Certamente. Non c'è dubbio che DMS sia incredibilmente potente, quasi al punto di magia (alla Arthur C. Clarke), e mentre il clang è fantastico, in realtà è solo un frontend C ++ ben scritto, di cui ce ne sono molti. Un gran numero di piccoli passi avanti, cioè, non sarebbe ancora giusto confrontarlo con DMS. Purtroppo, anche con strumenti così potenti a nostra disposizione, il software funzionante non si scrive da solo. Deve ancora nascere attraverso un'attenta traduzione usando gli strumenti o (praticamente sempre l'opzione superiore) scritta di fresco.
Steven Lu,

Non puoi permetterti di costruire strumenti come Clang o DMS da zero. Di solito non puoi permetterti di lanciare l'applicazione che hai scritto con un team di 10 in 5 anni. Avremo bisogno di tali strumenti sempre più spesso, poiché le dimensioni e la durata del software continuano a crescere.
Ira Baxter,

@StevenLu: Bene, DMS ti ringrazia per il complimento, ma non c'è nulla di magico. DMS ha il vantaggio di quasi 2 decenni lineari di ingegneria e una piattaforma architettonica pulita (aw, shucks, YMMV) che ha resistito abbastanza bene. Allo stesso modo, Clang ha molta buona ingegneria in esso. Sono d'accordo, non sono stati progettati per risolvere esattamente lo stesso problema ... Lo scopo di DMS è esplicitamente inteso essere più ampio quando si tratta di manipolazione simbolica del programma, e molto più piccolo quando si tratta di essere un compilatore di produzione.
Ira Baxter,

4

La metaprogrammazione in C ++ con l'API dei compilatori (anziché utilizzare i modelli) è davvero interessante e praticamente possibile. Poiché la metaprogrammazione non è (ancora) standardizzata, si verrà collegati a un compilatore specifico, il che non è il caso dei modelli.

Quindi, mi chiedo perché tutti non lo stiano già facendo. È che questa funzionalità di clang è così nuova e nessuno ha familiarità con l'enorme gerarchia di classi dell'AST di clang? Non può essere.

Molte persone lo fanno, ma in altre lingue. La mia opinione è che la maggior parte degli sviluppatori C ++ (o Java o C) non ne vede la necessità (forse giustamente) o non sono abituati agli approcci di metaprogrammazione; Penso anche che siano soddisfatti delle funzionalità di refactoring / generazione di codice del loro IDE e che qualsiasi cosa più elaborata possa essere vista troppo complessa / difficile da mantenere / difficile da eseguire il debug. Senza strumenti adeguati, potrebbe essere vero. Dovresti anche tenere conto dell'inerzia e di altri problemi non tecnici, come assumere e / o formare le persone.

A proposito, dal momento che menzioniamo Common Lisp e il suo sistema macro (vedi la risposta di Basile), devo dire che proprio ieri è stato rilasciato Clasp (non sono affiliato):

Clasp intende essere un'implementazione Common Lisp conforme che viene compilata in LLVM IR. Inoltre, espone le librerie Clang (AST, Matcher) allo sviluppatore.

  • Innanzitutto, ciò significa che potresti scrivere in CL e non usare più C ++, tranne quando usi le sue librerie (e se hai bisogno di macro, usa le macro CL).

  • In secondo luogo, è possibile scrivere strumenti in CL per il codice C ++ esistente (analisi, refactoring, ...).


3

Diversi compilatori C ++ hanno API più o meno documentate e stabili, in particolare la maggior parte dei compilatori di software gratuiti.

Clang / LLVM è principalmente un grande set di librerie e potresti usarle.

GCC recente accetta i plug-in . In particolare, è possibile estenderlo utilizzando MELT (che è esso stesso un meta-plugin, che fornisce un linguaggio specifico di dominio di livello superiore per estendere GCC).

Si noti che la sintassi di C ++ non è facilmente estendibile in GCC (e probabilmente nemmeno in Clang), ma è possibile aggiungere i propri pragmi, builtin, attributi e passaggi del compilatore per fare ciò che si desidera (magari fornendo anche alcune macro del preprocessore che invocano queste cose per fornire una sintassi intuitiva).

Potresti essere interessato a linguaggi e compilatori multi-stage, vedi ad esempio l' implementazione di linguaggi multi-stage utilizzando AST, Gensym e il documento di riflessione di C.Calcagno et al. e aggirare MetaOcaml . Dovresti certamente guardare all'interno delle macro strutture di Common Lisp . E potresti essere interessato da librerie JIT come libjit , GNU lightning , persino LLVM , o semplicemente - in fase di esecuzione! - generare un codice C ++, fork una sua compilazione in una libreria dinamica di oggetti condivisi, quindi aprire (3) che hanno condiviso oggetto. Il blog di J.Pitrat è anche collegato a tali approcci riflessivi. E anche RefPerSys .


Interessante. È molto bello vedere GCC continuare ad evolversi qui. Questa non è una risposta che si rivolge a tutto ciò che ho chiesto, ma mi piace comunque.
Steven Lu,

Ri: le tue nuove modifiche ... Questo è un buon punto sulla riscrittura del codice stesso. Questo in realtà inizia a portare tale capacità di meta-programma anche in C ++, molto più facilmente accessibile rispetto a prima, il che è anche abbastanza interessante.
Steven Lu,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.