È possibile creare un interprete "bootstrap" indipendente dall'interprete originale?


21

Secondo Wikipedia, il termine "bootstrap" nel contesto della scrittura di compilatori significa questo :

In informatica, il bootstrap è il processo di scrittura di un compilatore (o assemblatore) nel linguaggio di programmazione di origine che intende compilare. L'applicazione di questa tecnica porta a un compilatore self-hosting.

E capisco come funzionerebbe. Tuttavia, la storia sembra essere leggermente diversa per gli interpreti. Ora, ovviamente, è possibile scrivere un interprete self-hosting. Non è quello che sto chiedendo. Quello che in realtà sto chiedendo è: è possibile rendere un interprete autonomo indipendente dall'originale, primo interprete . Per spiegare cosa intendo, considera questo esempio:

Tu scrivi la tua prima versione interprete nel linguaggio X , e l'interprete è per un nuovo linguaggio che si sta creando, chiamato Y . Per prima cosa usi il compilatore di language X per creare un eseguibile. Ora è possibile interpretare i file scritti nella nuova lingua Y utilizzando l'interprete scritto in linguaggio X .

Ora, per quanto ho capito, di essere in grado di "bootstrap" l'interprete che hai scritto in un linguaggio X , avresti bisogno di riscrivere l'interprete nel linguaggio Y . Ma qui è il problema: anche se si fa riscrivere l'intero interprete nel linguaggio Y , si sta ancora andando ad avere bisogno l'originale interprete che hai scritto in linguaggio X . Perché per eseguire l'interprete nella lingua Y , dovrai interpretare i file di origine. Ma cosa interpreterà esattamente i file sorgente? Beh, non può essere niente, ovviamente, quindi sei costretto a usare ancora il primo interprete.

Indipendentemente dal numero di nuovi interpreti che scrivi nella lingua Y , dovrai sempre usare il primo interprete scritto in X per interpretare gli interpreti successivi. Questo sembra essere un problema semplicemente a causa della natura degli interpreti.

Tuttavia , il rovescio della medaglia, questo articolo di Wikipedia sugli interpreti in realtà parla di interpreti self-hosting . Ecco un piccolo estratto che è rilevante:

Un auto-interprete è un interprete del linguaggio di programmazione scritto in un linguaggio di programmazione che può interpretare se stesso; un esempio è un interprete BASIC scritto in BASIC. Gli auto-interpreti sono correlati ai compilatori self-hosting.

Se non esiste un compilatore per la lingua da interpretare, la creazione di un interprete automatico richiede l'implementazione della lingua in una lingua host (che può essere un altro linguaggio di programmazione o assemblatore). Avendo un primo interprete come questo, il sistema viene avviato e le nuove versioni dell'interprete possono essere sviluppate nella lingua stessa

Non mi è ancora chiaro, tuttavia, come si farebbe esattamente. Sembra che, indipendentemente da ciò, sarai sempre costretto a utilizzare la prima versione dell'interprete scritta nella lingua host.

Ora l'articolo sopra menzionato si collega a un altro articolo in cui Wikipedia fornisce alcuni esempi di presunti interpreti self-hosting . Ad un esame più attento, tuttavia, sembra che la parte "interpretante" principale di molti di quegli interpreti self-hosting (in particolare alcuni dei più comuni come PyPy o Rubinius) siano effettivamente scritti in altre lingue come C ++ o C.

Quindi è possibile ciò che descrivo sopra? Un interprete autonomo può essere indipendente dal suo host originale? In tal caso, come si farebbe esattamente?

Risposte:


24

La risposta breve è: hai ragione nei tuoi sospetti, hai sempre bisogno di un altro interprete scritto in X o di un compilatore da Y in un'altra lingua per la quale hai già un interprete. Gli interpreti eseguono, i compilatori traducono solo da una lingua all'altra, ad un certo punto del tuo sistema, ci deve essere un interprete ... anche solo la CPU.

Indipendentemente dal numero di nuovi interpreti che scrivi nella lingua Y , dovrai sempre usare il primo interprete scritto in X per interpretare gli interpreti successivi. Questo sembra essere un problema semplicemente a causa della natura degli interpreti.

Corretta. Che cosa si può fare è scrivere un compilatore da Y a X (o un'altra lingua per la quale si dispone di un interprete), e si può anche fare in Y . Quindi puoi eseguire il tuo compilatore Y scritto in Y sull'interprete Y scritto in X (o sull'interprete Y scritto in Y in esecuzione sull'interprete Y scritto in X , oppure sull'interprete Y scritto in Y in esecuzione sull'interprete Y scritto in Y in esecuzione su Yinterprete scritto in X , o ... all'infinito) per compilare il tuo interprete Y scritto in Y in X , in modo da poterlo eseguire su un interprete X. In questo modo, ti sei sbarazzato del tuo interprete Y scritto in X , ma ora hai bisogno dell'interprete X (sappiamo che ne abbiamo già uno, però, poiché altrimenti non potremmo eseguire l' interprete X scritto in Y ) e tu ho dovuto scrivere prima un compilatore da Y a X.

Tuttavia , il rovescio della medaglia, l'articolo di Wikipedia sugli interpreti in realtà parla di interpreti self-hosting. Ecco un piccolo estratto che è rilevante:

Un auto-interprete è un interprete del linguaggio di programmazione scritto in un linguaggio di programmazione che può interpretare se stesso; un esempio è un interprete BASIC scritto in BASIC. Gli auto-interpreti sono correlati ai compilatori self-hosting.

Se non esiste un compilatore per la lingua da interpretare, la creazione di un interprete automatico richiede l'implementazione della lingua in una lingua host (che può essere un altro linguaggio di programmazione o assemblatore). Avendo un primo interprete come questo, il sistema viene avviato e le nuove versioni dell'interprete possono essere sviluppate nella lingua stessa

Non mi è ancora chiaro, tuttavia, come si farebbe esattamente. Sembra che, indipendentemente da ciò, sarai sempre costretto a utilizzare la prima versione dell'interprete scritta nella lingua host.

Corretta. Nota che l'articolo di Wikipedia afferma esplicitamente che hai bisogno di una seconda implementazione della tua lingua e non dice che puoi sbarazzarti della prima.

Ora l'articolo sopra menzionato si collega a un altro articolo in cui Wikipedia fornisce alcuni esempi di presunti interpreti self-hosting. Ad un esame più attento, tuttavia, sembra che la parte "interpretante" principale di molti di quegli interpreti self-hosting (in particolare alcuni dei più comuni come PyPy o Rubinius) siano effettivamente scritti in altre lingue come C ++ o C.

Ancora una volta, corretto. Questi sono esempi davvero negativi. Prendi Rubinio, per esempio. Sì, è vero che la parte Ruby di Rubinius è ospitata autonomamente, ma è un compilatore, non un interprete: si compila dal codice sorgente Ruby al bytecode Rubinius. La parte dell'interprete OTOH non è auto-ospitata: interpreta il bytecode di Rubinius, ma è scritta in C ++. Quindi, chiamare Rubinius come "interprete auto-ospitato" è sbagliato: la parte ospitata non è un interprete e la parte interprete non è ospitata da sé .

PyPy è simile, ma anche più errato: non è nemmeno scritto in Python, in primo luogo, è scritto in RPython, che è un linguaggio diverso. È sintatticamente simile a Python, semanticamente un "sottoinsieme esteso", ma in realtà è un linguaggio tipicamente statico all'incirca sullo stesso livello di astrazione di Java, e la sua implementazione è un compilatore con backend multipli che compila RPython in codice sorgente C, ECMAScript codice sorgente, codice byte CIL, codice byte JVM o codice sorgente Python.

Quindi è possibile ciò che descrivo sopra? Un interprete self-host può essere indipendente dal suo host originale? In tal caso, come si farebbe esattamente?

No, non da solo. Dovresti mantenere l'interprete originale o scrivere un compilatore e compilare il tuo interprete personale.

Ci sono alcune VM meta-circolari, come Klein (scritto in Self ) e Maxine (scritto in Java). Si noti, tuttavia, che qui la definizione di "meta-circolare" è ancora diversa: queste macchine virtuali non sono scritte nella lingua che eseguono: Klein esegue il bytecode Self ma è scritto in Self, Maxine esegue il bytecode JVM ma è scritto in Java. Tuttavia, il codice sorgente Self / Java della VM viene effettivamente compilato in bytecode Self / JVM e quindi eseguito dalla VM, quindi quando la VM viene eseguita, è nella lingua che esegue. Uff.

Si noti inoltre che questo è diverso dalle macchine virtuali come SqueakVM e Jikes RVM . Jikes è scritto in Java e SqueakVM è scritto in Slang (un sottoinsieme sintattico e semantico tipicamente statico di Smalltalk all'incirca sullo stesso livello di astrazione di un assemblatore di alto livello) ed entrambi vengono compilati staticamente in codice nativo prima di essere eseguiti. Non corrono dentro di sé. È possibile , tuttavia, eseguirli sopra di sé (o sopra un'altra VM / JVM Smalltalk). Ma questo non è "meta-circolare" in questo senso.

Maxine e Klein, OTOH docorrere dentro di sé; eseguono il proprio bytecode usando la propria implementazione. Questo è davvero strabiliante! Consente alcune interessanti opportunità di ottimizzazione, ad esempio poiché la VM si esegue da sola insieme al programma utente, può incorporare le chiamate dal programma utente alla VM e viceversa, ad esempio la chiamata al garbage collector o l'allocatore di memoria può essere incorporata nell'utente codice e richiamate riflettenti nel codice utente possono essere integrate nella VM. Inoltre, tutti i trucchi di ottimizzazione intelligenti che fanno le moderne VM, in cui guardano il programma in esecuzione e lo ottimizzano in base al carico di lavoro e ai dati effettivi, la VM può applicare quegli stessi trucchi a se stessa mentre esegue il programma utente mentre il programma utente sta eseguendo il carico di lavoro specifico. In altre parole, la VM altamente specializzato per sé cheprogramma particolare che esegue quel particolare carico di lavoro.

Tuttavia, noti che ho aggirato l'uso della parola "interprete" sopra, e ho sempre usato "esegui"? Bene, quelle macchine virtuali non sono costruite attorno agli interpreti, sono costruite attorno a compilatori (JIT). In seguito è stato aggiunto un interprete a Maxine, ma è sempre necessario il compilatore: è necessario eseguire la VM una volta sopra un'altra VM (ad esempio Oracle HotSpot nel caso di Maxine), in modo che la VM possa (JIT) compilarsi da sola. Nel caso di Maxine, JIT compilerà la propria fase di avvio, quindi serializzerà quel codice nativo compilato su un'immagine VM bootstrap e applicherà un bootloader molto semplice di fronte (l'unico componente della VM scritto in C, anche se è solo per comodità , potrebbe essere anche in Java). Ora puoi usare Maxine per eseguire se stesso.


Yeesh . Non ho mai saputo che il mondo degli interpreti self-hosting fosse così appiccicoso! Grazie per aver dato una bella panoramica però.
Christian Dean,

1
Haha, beh, perché il mondo dovrebbe essere meno incline alla mente del concetto? ;-)
Jörg W Mittag,

3
Immagino che uno dei problemi sia che le persone spesso giocano in modo veloce e sciolto con le lingue coinvolte. Ad esempio, Rubinius è di solito chiamato "Ruby in Ruby", ma questa è solo metà della storia. Sì, a rigor di termini , il compilatore Ruby in Rubinius è scritto in Ruby, ma la VM che esegue il bytecode non lo è. E ancora peggio: PyPy viene spesso chiamato "Python in Python", tranne per il fatto che non esiste una sola riga di Python. Il tutto è scritto in RPython, progettato per essere familiare ai programmatori Python, ma non è Python . Allo stesso modo SqueakVM: non è scritto in Smalltalk, ma ...
Jörg W Mittag

... è scritto in gergo, che secondo le persone che l'hanno effettivamente codificato, è anche peggio di C nelle sue capacità di astrazione. L'unico vantaggio di Slang è che si tratta di un sottoinsieme corretto di Smalltalk, il che significa che è possibile svilupparlo (ed eseguire e soprattutto eseguire il debug della VM su) un potente IDE Smalltalk.
Jörg W Mittag,

2
Solo per aggiungere un'altra complicazione: alcuni linguaggi interpretati (ad esempio FORTH e possibilmente TeX) sono in grado di scrivere un'immagine di memoria caricabile del sistema in esecuzione, come file eseguibile. In tal senso, tali sistemi possono quindi funzionare senza l'interprete originale. Ad esempio, una volta ho scritto un interprete FORTH utilizzando una versione a 16 bit di FORTH per "interpretare in modo incrociato" una versione a 32 bit per una CPU diversa ed eseguire su un sistema operativo diverso. (Nota, il linguaggio FORTH include un proprio assemblatore, quindi la "FORTH VM" (che in genere è solo 10 o 20 istruzioni per il codice macchina) può essere scritta in FORTH stessa.)
alephzero

7

Hai ragione nel notare che un interprete self-hosting richiede ancora che un interprete esegua se stesso, e non può essere avviato come nello stesso senso di un compilatore.

Tuttavia, una lingua ospitata autonomamente non è la stessa cosa di un interprete autonomo. Generalmente è più semplice costruire un interprete che costruire un compilatore. Pertanto, per implementare una nuova lingua, potremmo prima implementare un interprete in una lingua non correlata. Quindi possiamo usare quell'interprete per sviluppare un compilatore per la nostra lingua. Il linguaggio viene quindi ospitato autonomamente, poiché il compilatore viene interpretato. Il compilatore può quindi compilare se stesso e può quindi essere considerato completamente avviato.

Un caso speciale di ciò è un runtime di compilazione JIT self-hosting. Può iniziare con un interprete in una lingua host, che quindi utilizza il nuovo linguaggio per implementare la compilazione JIT, dopodiché il compilatore JIT può compilare se stesso. Sembra un interprete self-hosting, ma elude il problema degli infiniti interpreti. Questo approccio viene utilizzato, ma non è ancora molto comune.

Un'altra tecnica correlata è un interprete estensibile, in cui possiamo creare estensioni nella lingua interpretata. Ad esempio, potremmo implementare nuovi codici operativi nella lingua. Ciò può trasformare un interprete bare-bones in un interprete ricco di funzionalità, purché si evitino dipendenze circolari.

Un caso che si verifica abbastanza comunemente è la capacità del linguaggio di influenzare la propria analisi, ad esempio come macro valutate al tempo di analisi. Poiché il linguaggio macro è lo stesso del linguaggio in elaborazione, tende ad essere molto più ricco di funzionalità rispetto ai linguaggi macro dedicati o limitati. Tuttavia, è corretto notare che la lingua che esegue l'estensione è leggermente diversa dalla lingua dopo l'estensione.

Quando si usano interpreti "reali" ospitati da sé, ciò avviene di solito per motivi di istruzione o di ricerca. Ad esempio l'implementazione di un interprete per Scheme all'interno di Scheme è un ottimo modo per insegnare i linguaggi di programmazione (vedi SICP).

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.