Regole generali per la scrittura di un compilatore X su Z in Y


9

Supponiamo che X sia la lingua di input, Z sia la lingua di output, quindi f sia il compilatore, che è scritto nella lingua Y.

f = X -> Z

Dato che f è solo un programma, penso che Y possa essere qualsiasi lingua, giusto? Quindi possiamo avere compilatori f1, f2, ognuno scritto in Y1, Y2.

f1 = f Y1    
f2 = f Y2

g = Z -> M
h = g . f    # We get a compiler X -> M

Prendi ad esempio il compilatore di cpython, X è Python, Z è il codice Python VM, Y è C.

cpython = Python -> PythonVMCode C
interpreter = PythonVMCode -> Nothing
interpreter2 = PythonVMCode -> MachineCode

I sorgenti Python vengono compilati nel codice VM Python, i file .pyc, quindi interpretati dall'interprete. Sembra che sia possibile che esista un compilatore che può eseguire direttamente Python -> MachineCode, anche se molto difficile da implementare:

   hardpython = interpreter2 . cpython 

Possiamo anche scrivere un altro compilatore per fare il lavoro Python -> PythonVMCode, in un'altra lingua, dire Python stesso.

mypython = Python -> PythonVMCode Python
mypython2 = Python -> PythonVMCode Ruby

Ora, ecco l'esempio complicato di PyPy. Sono solo un principiante di PyPy, correggimi se sbaglio:

PyPy doc http://doc.pypy.org/en/latest/architecture.html#pypy-the-translation-framework

Il nostro obiettivo è fornire una possibile soluzione al problema degli implementatori di linguaggi: dover scrivere interpreti l * o * p per linguaggi dinamici e piattaforme p con decisioni di progettazione cruciali.

Possiamo pensare che sia X, p sia Y. Esiste un programma che traduce tutti i programmi RPython in C:

 rpython_compiler = RPython -> C  Python

 pypy = Python -> Nothing RPython

 translate = compile the program pypy written in RPython using rpython_compiler

 py2rpy = Python -> RPython  Python
 py2c = Python -> C Python 
 py2c = rpython_compiler . py2rpy

I programmi RPython sono proprio come le istruzioni della VM, rpython_compiler è la VM.

q1. pypy è l'interprete, un programma RPython in grado di interpretare il codice Python, non esiste un linguaggio di output, quindi non possiamo considerarlo come un compilatore, giusto?

Inserito il:

  • Ho appena scoperto che anche se dopo la traduzione, pypy è ancora un interprete, solo questa volta scritto in C.
  • Se guardiamo in profondità nel pypy dell'interprete, credo che debba esistere una sorta di compilatore, che compila i sorgenti Python in alcuni AST, quindi eseguiamo

come questo:

compiler_inside_pypy = Python -> AST_or_so

q2. Può esistere il compilatore py2rpy, trasformando tutti i programmi Python in RPython? In quale lingua è scritta è irrilevante. Se sì, otteniamo un altro compilatore py2c. Qual è la differenza tra pypy e py2rpy in natura? Py2rpy è molto più difficile da scrivere di pypy?

q3. Ci sono alcune regole o teorie generali disponibili su questo?

Più compilatori:

gcc_c = C -> asm? C  # not sure, gimple or rtl?
g++ =   C++ -> asm? C
clang = C -> LLVM_IR  C++
jython = Python -> JVMCode java
ironpython = Python -> CLI C#

q4. Dato f = X -> Z, un programma P scritto in X. Quando vogliamo velocizzare P, cosa possiamo fare? Modi possibili:

  • riscrivi P in un algoritmo più efficiente

  • riscrivi f per generare una Z migliore

  • se Z viene interpretato, scrivi un interprete Z migliore (PyPy è qui?)

  • velocizzare i programmi scritti in Z in modo ricorsivo

  • ottenere una macchina migliore

ps. Questa domanda non riguarda gli aspetti tecnici di come scrivere un compilatore, ma la fattibilità e la complessità di scrivere un certo compilatore.


Non direttamente correlato, ma in qualche modo un concetto simile: en.wikipedia.org/wiki/Supercompilation
SK-logic

1
Non sono sicuro che questa domanda si adatti davvero allo Stack Overflow, soprattutto perché ci sono così tante domande secondarie, ma ammiro ancora il pensiero che è stato affrontato.

4
Nonostante ciò che potrebbe esserti stato insegnato, non è richiesto un AST: è semplicemente una strategia che alcuni compilatori usano.

1
Probabilmente questo appartiene a cstheory.stackexchange.com
9000,

3
L'implementazione Python di PyPy, come la maggior parte degli "interpreti", è in realtà un compilatore bytecode e un interprete per quel formato bytecode in uno.

Risposte:


4

q1. pypy è l'interprete, un programma RPython in grado di interpretare il codice Python, non esiste un linguaggio di output, quindi non possiamo considerarlo come un compilatore, giusto?

PyPy è simile a CPython, entrambi hanno un compilatore + interprete. CPython ha un compilatore scritto in C che compila il bytecode Python in Python VM, quindi esegue il bytecode in un interprete scritto in C. PyPy ha un compilatore scritto in RPython che compila Python in bytecode Python VM, quindi lo esegue in PyPy Interpreter scritto in RPython.

q2. Può esistere il compilatore py2rpy, trasformando tutti i programmi Python in RPython? In quale lingua è scritta è irrilevante. Se sì, otteniamo un altro compilatore py2c. Qual è la differenza tra pypy e py2rpy in natura? Py2rpy è molto più difficile da scrivere di pypy?

Può esistere un compilatore py2rpy? Teoricamente si. La completezza di Turing lo garantisce.

Un metodo da costruire py2rpyè semplicemente includere il codice sorgente di un interprete Python scritto in RPython nel codice sorgente generato. Un esempio di compilatore py2rpy, scritto in Bash:

// suppose that /pypy/source/ contains the source code for pypy (i.e. Python -> Nothing RPython)
cp /pypy/source/ /tmp/py2rpy/pypy/

// suppose $inputfile contains an arbitrary Python source code
cp $inputfile /tmp/py2rpy/prog.py

// generate the main.rpy
echo "import pypy; pypy.execfile('prog.py')" > /tmp/py2rpy/main.rpy

cp /tmp/py2rpy/ $outputdir

ora ogni volta che devi tradurre un codice Python in codice RPython, chiami questo script, che produce - in $ outputdir - un RPython main.rpy, il codice sorgente Python Interpreter di RPython e un BLOB binario prog.py. E quindi puoi eseguire lo script RPython generato chiamando rpython main.rpy.

(nota: poiché non ho familiarità con il progetto rpython, la sintassi per chiamare l'interprete rpython, la capacità di importare pypy e fare pypy.execfile e l'estensione .rpy è puramente inventata, ma penso che tu capisca il punto)

q3. Ci sono alcune regole o teorie generali disponibili su questo?

Sì, qualsiasi lingua di Turing Complete può teoricamente essere tradotta in qualsiasi lingua di Turing Complete. Alcune lingue possono essere molto più difficili da tradurre rispetto ad altre lingue, ma se la domanda è "è possibile?", La risposta è "sì"

q4. ...

Non ci sono domande qui.


Il tuo compilatore py2rpy è davvero intelligente. Mi porta a un'altra idea. 1. Il pypy deve essere scritto in RPython nel tuo compilatore? Tutto ciò che serve è qualcosa in grado di interpretare i file Python, giusto? 2. os.system ('python $ inputfile') può anche funzionare se è supportato in RPython. Non sono sicuro che possa ancora essere chiamato compilatore, almeno non letteralmente.

Pypy sta ancora usando la VM Python? Adesso è chiaro pypy_the_compiler = Python -> PythonVMCode RPython, pypy_the_interpreter = PythonVMCode -> Niente RPython, cpython_the_compiler = Python -> PythonVMCode C, cpython_the_interpreter = PythonVMCode -> Niente C

@jaimechen: Does pypy have to be written in RPython in your compiler?No, non deve essere scritto in RPython, ma RPython deve essere in grado di dire all '"interprete ausiliario" / "runtime" di eseguire un codice Python. Sì, è vero che questo non è un "compilatore" in senso pratico, ma è una prova costruttiva che è possibile scrivere Python -> RPython. Is pypy still using the Python VM?Credo che pypy non usi affatto CPython (potrei sbagliarmi), invece PyPy ha la sua implementazione di "Python VM" scritta in RPython.
Lie Ryan,

@jaimechen: un compilatore più pratico potrebbe eventualmente analizzare il file di input per sequenze di codici che sa come compilare e compilare separatamente e anche un modo per saltare avanti e indietro tra il "ricompilato da RPython" Python e "interprete- aiutato "Python. Può anche utilizzare tecniche comunemente utilizzate nella compilazione JIT per rilevare se un determinato input può produrre output diversi a causa delle differenze nella semantica di RPython e Python e fallback all'interpretazione in quei casi. Tutte quelle sono raffinatezze che potrebbero essere viste in un Python -> RPythoncompilatore più pratico .
Lie Ryan,

Forse un vincolo dovrebbe essere aggiunto qui: trasforma la macchina a stati X in macchina a stati Z, senza l'aiuto di una terza macchina esistente. Questo è il caso in cui X è completamente nuovo, nessun compilatore o interprete è mai esistito finora.
Jaimechen,

2

Per rispondere solo a q2, esiste un libro di compilazione di William McKeeman in cui la teoria dei compilatori per la lingua X scritta nella lingua Y che produce la lingua di output Z viene esplorata attraverso un sistema di diagrammi a T. Pubblicato negli anni '70, titolo non disponibile, scusa.



1

q1. In genere, un interprete non è un compilatore. La differenza chiave tra un compilatore e un interprete è che un interprete inizia di nuovo, con il codice sorgente nella lingua di origine, ogni volta. Se invece il tuo pypy fosse pyAST, o codice pyP, e quindi avessi un interprete AST o codice P, allora potresti chiamare pyAST un compilatore. Ecco come funzionava il vecchio compilatore UCSD PASCAL (e molti altri): compilavano un codice P, che veniva interpretato durante l'esecuzione del programma. (Anche .NET fornisce qualcosa di simile, quando la compattezza del codice oggetto generato è molto più importante della velocità.)

q2. Sì, naturalmente. Vedi UCSD PASCAL (e un sacco di altri).

q3. Scava tra i testi classici in informatica. Leggi su Concurrent PASCAL, di Per Brinch-Hansen (se la memoria mi serve). Molto è stato scritto su compilatori e generazione di codice. Generare uno pseudocodice indipendente dalla macchina è di solito molto più semplice della generazione del codice macchina: lo pseudocodice è solitamente privo di stranezze che le macchine reali invariabilmente contengono.

q4. Se vuoi che l'oggetto generato venga eseguito più velocemente, rendi il compilatore più intelligente, per ottimizzare l'ottimizzazione. Se il tuo oggetto viene interpretato, prendi in considerazione di spingere le operazioni più complesse verso il basso in pseudo-istruzioni primitive (CISC vs. RISC è l'analogia), quindi fai del tuo meglio per ottimizzare il frack out del tuo interprete.

Se vuoi che il tuo compilatore funzioni più velocemente, devi guardare TUTTO ciò che fa, incluso ripensare il tuo codice sorgente. Dopo aver caricato il compilatore stesso, la parte più lunga della compilazione è SEMPRE la lettura del codice sorgente nel compilatore. (Considera C ++ per esempio. Tutte le altre cose sono relativamente uguali, un compilatore che deve abbattere 9.000 (o forse 50.000) righe di file #include per compilare un semplice programma "Hello, World" non sarà mai veloce come uno deve solo leggere quattro o cinque righe.)

Non ricordo dove l'ho letto, ma il compilatore originale Oberon all'ETH-Zurigo aveva un meccanismo di tabella dei simboli molto sofisticato, abbastanza elegante. Il benchmark di Wirth per le prestazioni del compilatore è stato il tempo impiegato dal compilatore per compilare se stesso. Una mattina entrò, tirò fuori la splendida tabella dei simboli degli ultra-alberi multi-link e la sostituì con una semplice matrice lineare e ricerche lineari diritte. Gli studenti laureati nel suo gruppo sono stati SHOCKED. Dopo la modifica, il compilatore è stato più veloce, poiché i moduli che stava compilando erano sempre abbastanza piccoli da consentire all'elegante mostro di imporre un overhead totale maggiore rispetto all'array lineare e alla ricerca lineare.


1
Grazie. Un compilatore "compila", mentre un interprete "esegue", possono esserci più informazioni sui due tipi di programmi, come se i loro tipi fossero diversi?
Jaimechen,

1

Le tue domande, come affermato, mi portano a credere che ciò che veramente vuoi / hai bisogno sia una spiegazione di cosa sia un compilatore, di cosa sia un interprete e delle differenze tra i due.

Un compilatore esegue il mapping di un programma scritto nella lingua X a un programma funzionalmente equivalente scritto nella lingua Y. Ad esempio, un compilatore da Pascal a C potrebbe compilare

function Square(i: Integer)
begin
    Square := i * i
end

per

int Square(int i)
{
    return i * i;
}

La maggior parte dei compilatori compilano "verso il basso", quindi compilano linguaggi di programmazione di livello superiore in linguaggi di livello inferiore, il cui linguaggio di livello inferiore è il codice macchina.

La maggior parte dei compilatori viene compilata direttamente in codice macchina, ma alcuni (in particolare i linguaggi Java e .NET) vengono compilati in "bytecode" ( bytecode Java e CIL ). Pensa al bytecode come codice macchina per un ipotetico computer. Questo bytecode viene quindi interpretato o JITted quando viene eseguito (ne parleremo più avanti).

Un interprete esegue un programma scritto in una lingua Z. Un interprete legge un programma bit per bit, eseguendolo mentre procede. Per esempio:

int i = 0;
while (i < 1)
{
    i++
}
return i;

Immagina che l'interprete guardi quella linea di programma per riga, esaminando la riga, eseguendo ciò che fa, guardando la riga successiva e così via.

Il miglior esempio di interprete è la CPU del tuo computer. Interpreta il codice macchina e lo esegue. Come funziona la CPU è specificato da come è fisicamente costruita. Il modo in cui funziona un programma interprete è determinato dall'aspetto del suo codice. La CPU quindi interpreta ed esegue il programma interprete, che a sua volta interpreta ed esegue il suo input. Puoi interpretare gli interpreti in questo modo.

Un JITter è un compilatore Just-In-Time. Un JITter è un compilatore. L'unica differenza è il tempo in cui viene eseguita: la maggior parte dei programmi viene scritta, compilata, spedita ai loro utenti e quindi eseguita, ma bytecode Java e CIL vengono spediti prima ai loro utenti, e poi appena prima di essere eseguiti vengono compilati sulla macchina codice dei loro utenti.

C # -> (compilare) -> CIL -> spedito al cliente -> (compilare poco prima dell'esecuzione) -> codice macchina -> (eseguire)

L'ultima cosa che vorresti sapere è la completezza di Turing ( link ). Un linguaggio di programmazione è Turing Complete se è in grado di calcolare tutto ciò che una " macchina di Turing " può, cioè è almeno "potente" come una macchina di Turing. La tesi di Church-Turing afferma che una macchina di Turing è almeno potente quanto qualsiasi macchina che possiamo mai costruire. Ne consegue che ogni linguaggio completo di Turing è esattamente potente quanto la macchina Turing, e quindi tutti i linguaggi completi di Turing sono ugualmente potenti.

In altre parole, fintanto che il tuo linguaggio di programmazione è Turing completo (quasi tutti lo sono), non importa quale lingua scegli, poiché possono calcolare tutte le stesse cose. Questo significa anche che non è molto rilevante quale linguaggio di programmazione scegli per scrivere il tuo compilatore o il tuo interprete. Ultimo ma non meno importante, significa che puoi sempre scrivere un compilatore dalla lingua X a Y se X e Y sono entrambi Turing completi.

Nota che essere Turing completo non dice nulla sull'efficacia della tua lingua, né su tutti i dettagli di implementazione della tua CPU e altro hardware, né sulla qualità del compilatore che usi per la lingua. Inoltre, il tuo sistema operativo potrebbe decidere che il tuo programma non ha i diritti per aprire un file, ma ciò non impedisce la tua capacità di calcolare nulla - non ho deliberatamente definito il calcolo, dal momento che ciò richiederebbe un altro muro di testo.

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.