Ist es möglich, einen "Bootstrapped" -Interpreter unabhängig vom ursprünglichen Interpreter zu erstellen?


21

Laut Wikipedia bedeutet der Begriff "Bootstrapping" im Zusammenhang mit dem Schreiben von Compilern Folgendes :

In der Informatik ist Bootstrapping das Schreiben eines Compilers (oder Assemblers) in der Quellprogrammiersprache, die kompiliert werden soll. Das Anwenden dieser Technik führt zu einem selbsthostenden Compiler.

Und ich kann verstehen, wie das funktionieren würde. Für Dolmetscher scheint die Geschichte jedoch etwas anders zu sein. Jetzt ist es natürlich möglich, einen selbsthostenden Dolmetscher zu schreiben. Das frage ich nicht. Was ich eigentlich fragen: Ist es möglich , einen selbst gehosteten Dolmetscher unabhängig von der ursprünglichen, ersten Dolmetscher zu machen . Um zu erklären, was ich meine, betrachten Sie dieses Beispiel:

Sie schreiben Ihre erste Interpreter Version in Sprache X , und der Dolmetscher ist für eine neue Sprache , die Sie erstellen, genannt Y . Sie verwenden zuerst den Compiler von Sprache X , um eine ausführbare Datei zu erstellen. Sie können jetzt Dateien, die in Ihrer neuen Sprache Y geschrieben sind, mit dem in Sprache X geschriebenen Interpreter interpretieren .

Soweit ich weiß, müssen Sie den Interpreter in der Sprache Y neu schreiben, um den von Ihnen in der Sprache X geschriebenen Interpreter "booten" zu können . Aber hier ist der Haken: Auch wenn Sie den gesamten Interpreter in Sprache Y umschreiben , benötigen Sie den Originalinterpreter, den Sie in Sprache X geschrieben haben . Um den Interpreter in der Sprache Y auszuführen , müssen Sie die Quelldateien interpretieren. Aber was genau wird die Quelldateien interpretieren? Nun, es kann natürlich nichts sein, also müssen Sie immer noch den ersten Interpreter verwenden.

Unabhängig davon, wie viele neue Dolmetscher Sie in der Sprache Y schreiben , müssen Sie immer den ersten in X geschriebenen Dolmetscher verwenden, um die nachfolgenden Dolmetscher zu interpretieren. Dies scheint ein Problem zu sein, allein aufgrund der Art der Dolmetscher.

Auf der anderen Seite geht es in diesem Wikipedia-Artikel über Dolmetscher jedoch tatsächlich um selbsthostende Dolmetscher . Hier ist ein kleiner Auszug, der relevant ist:

Ein Selbstinterpreter ist ein Programmierspracheninterpreter, der in einer Programmiersprache geschrieben ist, die sich selbst interpretieren kann. Ein Beispiel ist ein in BASIC geschriebener BASIC-Interpreter. Selbstdolmetscher sind mit selbsthostenden Compilern verwandt.

Wenn für die zu interpretierende Sprache kein Compiler vorhanden ist, muss zum Erstellen eines Selbstinterpreters die Sprache in einer Host-Sprache (die möglicherweise eine andere Programmiersprache oder ein Assembler ist) implementiert werden. Mit einem solchen ersten Interpreter wird das System gebootet und neue Versionen des Interpreters können in der Sprache selbst entwickelt werden

Es ist mir jedoch immer noch nicht klar, wie genau dies geschehen würde. Es scheint, dass Sie immer die erste Version Ihres Dolmetschers verwenden müssen, die in der Host-Sprache geschrieben ist.

Nun verweist der oben erwähnte Artikel auf einen anderen Artikel, in dem Wikipedia einige Beispiele für vermeintliche selbsthostende Dolmetscher enthält . Bei näherer Betrachtung scheint es jedoch so zu sein, dass der Hauptteil der "Dolmetscher" vieler dieser selbsthostenden Dolmetscher (insbesondere einiger der gebräuchlicheren wie PyPy oder Rubinius) tatsächlich in anderen Sprachen wie C ++ oder C geschrieben ist.

Also ist das, was ich oben beschreibe, möglich? Kann ein selbst gehosteter Dolmetscher unabhängig von seinem ursprünglichen Host sein? Wenn ja, wie genau würde das geschehen?

Antworten:


24

Die kurze Antwort lautet: Sie haben Recht, Sie benötigen immer entweder einen anderen in X geschriebenen Dolmetscher oder einen Compiler von Y in eine andere Sprache, für die Sie bereits einen Dolmetscher haben. Interpreter werden ausgeführt, Compiler übersetzen nur von einer Sprache in eine andere. Irgendwann in Ihrem System muss es einen Interpreter geben. Auch wenn es sich nur um die CPU handelt.

Unabhängig davon, wie viele neue Dolmetscher Sie in der Sprache Y schreiben , müssen Sie immer den ersten in X geschriebenen Dolmetscher verwenden, um die nachfolgenden Dolmetscher zu interpretieren. Dies scheint ein Problem zu sein, allein aufgrund der Art der Dolmetscher.

Richtig. Sie können einen Compiler von Y nach X schreiben (oder eine andere Sprache, für die Sie einen Interpreter haben), und das können Sie sogar in Y tun . Dann können Sie Ihren Lauf Y - Compiler geschrieben in Y auf der Y - Interpreter in schriftlich X (oder auf der Y geschrieben Dolmetschern in Y auf dem Lauf Y - Interpreter in geschrieben X oder auf den Y - Interpreter in geschrieben Y auf der laufenden Y - Interpreter geschrieben in Y läuft auf dem YInterpreter in X oder… ad infinitum), um Ihren in Y bis X geschriebenen Y- Interpreter zu kompilieren , damit Sie ihn dann auf einem X- Interpreter ausführen können . Auf diese Weise haben Sie Ihren in X geschriebenen Y- Interpreter losgeworden , aber jetzt benötigen Sie den X- Interpreter (wir wissen, dass wir jedoch bereits einen haben, da wir sonst den in Y geschriebenen X- Interpreter nicht ausführen könnten ) und Sie musste zuerst einen Y- to- X- Compiler schreiben .

Auf der anderen Seite geht es in dem Wikipedia-Artikel über Dolmetscher jedoch tatsächlich um selbsthostende Dolmetscher. Hier ist ein kleiner Auszug, der relevant ist:

Ein Selbstinterpreter ist ein Programmierspracheninterpreter, der in einer Programmiersprache geschrieben ist, die sich selbst interpretieren kann. Ein Beispiel ist ein in BASIC geschriebener BASIC-Interpreter. Selbstdolmetscher sind mit selbsthostenden Compilern verwandt.

Wenn für die zu interpretierende Sprache kein Compiler vorhanden ist, muss zum Erstellen eines Selbstinterpreters die Sprache in einer Host-Sprache (die möglicherweise eine andere Programmiersprache oder ein Assembler ist) implementiert werden. Mit einem solchen ersten Interpreter wird das System gebootet und neue Versionen des Interpreters können in der Sprache selbst entwickelt werden

Es ist mir jedoch immer noch nicht klar, wie genau dies geschehen würde. Es scheint, dass Sie immer die erste Version Ihres Dolmetschers verwenden müssen, die in der Host-Sprache geschrieben ist.

Richtig. Beachten Sie, dass der Wikipedia-Artikel ausdrücklich besagt, dass Sie eine zweite Implementierung Ihrer Sprache benötigen und nicht, dass Sie die erste entfernen können.

Der oben erwähnte Artikel verweist nun auf einen anderen Artikel, in dem Wikipedia einige Beispiele für vermeintliche selbsthostende Dolmetscher enthält. Bei näherer Betrachtung scheint es jedoch so zu sein, dass der Hauptteil der "Dolmetscher" vieler dieser selbsthostenden Dolmetscher (insbesondere einiger der gebräuchlicheren wie PyPy oder Rubinius) tatsächlich in anderen Sprachen wie C ++ oder C geschrieben ist.

Wieder richtig. Das sind wirklich schlechte Beispiele. Nehmen wir zum Beispiel Rubinius. Ja, es ist wahr, dass der Ruby-Teil von Rubinius selbst gehostet wird, aber er ist ein Compiler und kein Interpreter: Er kompiliert Ruby-Quellcode in Rubinius-Bytecode. Der Interpreter-Teil OTOH ist nicht selbst gehostet: Er interpretiert den Rubinius-Bytecode, ist jedoch in C ++ geschrieben. Rubinius als "selbst gehosteten Interpreter" zu bezeichnen, ist also falsch: Der selbst gehostete Teil ist kein Interpreter , und der Interpreter- Teil ist nicht selbst gehostet .

PyPy ist ähnlich, aber noch falscher: Es ist nicht einmal in Python geschrieben, sondern in RPython, einer anderen Sprache. Es ist syntaktisch Python ähnlich, semantisch eine "erweiterte Untermenge", aber es ist tatsächlich eine statisch typisierte Sprache, die ungefähr auf derselben Abstraktionsebene wie Java liegt, und seine Implementierung ist ein Compiler mit mehreren Backends, der RPython zu C-Quellcode, ECMAScript, kompiliert Quellcode, CIL-Bytecode, JVM-Bytecode oder Python-Quellcode.

Also ist das, was ich oben beschreibe, möglich? Kann ein Self-Host-Interpreter unabhängig von seinem ursprünglichen Host sein? Wenn ja, wie genau würde das geschehen?

Nein, nicht alleine. Sie müssten entweder den Originalinterpreter behalten oder einen Compiler schreiben und Ihren Selbstinterpreter kompilieren.

Es gibt einige Meta-Circular-VMs wie Klein (geschrieben in Self ) und Maxine (geschrieben in Java). Beachten Sie jedoch, dass die Definition von "Meta-Circular" hier noch anders ist: Diese VMs sind nicht in der Sprache geschrieben, die sie ausführen: Klein führt Self-Bytecode aus, ist jedoch in Self geschrieben, Maxine führt JVM-Bytecode aus, ist jedoch in Java geschrieben. Der Self / Java-Quellcode der VM wird jedoch tatsächlich zu Self / JVM-Bytecode kompiliert und dann von der VM ausgeführt. Wenn die VM ausgeführt wird, ist sie also in der Sprache, die sie ausführt. Puh.

Beachten Sie auch, dass sich dies von VMs wie der SqueakVM und der Jikes RVM unterscheidet . Jikes ist in Java geschrieben, und SqueakVM ist in Slang geschrieben (eine statisch typisierte syntaktische und semantische Teilmenge von Smalltalk, die ungefähr auf derselben Abstraktionsebene wie ein Assembler auf hoher Ebene liegt). Beide werden vor der Ausführung statisch zu nativem Code kompiliert. Sie rennen nicht in sich hinein. Sie können sie jedoch auf sich selbst (oder auf einer anderen Smalltalk VM / JVM) ausführen . Aber das ist in diesem Sinne nicht "meta-rund".

Maxine und Klein, OTOH doin sich laufen; Sie führen ihren eigenen Bytecode mit ihrer eigenen Implementierung aus. Das ist wirklich aufregend! Dies bietet einige interessante Optimierungsmöglichkeiten, zum Beispiel, da die VM sich selbst zusammen mit dem Benutzerprogramm ausführt, Inline-Aufrufe vom Benutzerprogramm an die VM und umgekehrt, z. B. Aufrufe an den Garbage Collector, oder der Speicherzuordner können in den Benutzer eingebunden werden Code und reflektierende Rückrufe im Benutzercode können in die VM eingefügt werden. Bei all den cleveren Optimierungs-Tricks moderner VMs, bei denen sie das ausführende Programm beobachten und es in Abhängigkeit von der tatsächlichen Arbeitslast und den Daten optimieren, kann die VM dieselben Tricks auf sich selbst anwenden, während sie das Benutzerprogramm während des Benutzerprogramms ausführt führt die spezifische Arbeitslast aus. Mit anderen Worten, spezialisierte sich die VM hoch selbst für diebestimmtes Programm, das diese bestimmte Arbeitslast ausführt.

Beachten Sie jedoch, dass ich die Verwendung des Wortes "Interpreter" oben umgangen und immer "Ausführen" verwendet habe? Diese VMs basieren nicht auf Interpreter, sondern auf JIT-Compilern. Es wurde später ein Interpreter zu Maxine hinzugefügt, aber Sie benötigen immer den Compiler: Sie müssen die VM einmal über einer anderen VM ausführen (z. B. Oracle HotSpot im Fall von Maxine), damit sich die VM selbst kompilieren kann (JIT). Im Fall von Maxine kompiliert JIT seine eigene Boot-Phase, serialisiert dann den kompilierten nativen Code in ein Bootstrap-VM-Image und klebt einen sehr einfachen Bootloader voran (die einzige in C geschriebene Komponente der VM, obwohl dies nur der Einfachheit halber dient könnte es auch in Java sein). Jetzt können Sie Maxine verwenden, um sich selbst auszuführen.


Yeesh . Ich hätte nie gedacht, dass die Welt der Dolmetscher, die sich selbst hosten, so schwierig ist! Vielen Dank für den schönen Überblick.
Christian Dean

1
Haha, warum sollte die Welt weniger nervenaufreibend sein als das Konzept? ;-)
Jörg W Mittag

3
Ich denke, eines der Probleme ist, dass die Leute oft schnell und locker mit den beteiligten Sprachen spielen. Zum Beispiel wird Rubinius normalerweise "Ruby in Ruby" genannt, aber das ist nur die halbe Wahrheit. Ja, genau genommen ist der Ruby-Compiler in Rubinius in Ruby geschrieben, die VM, die den Bytecode ausführt, jedoch nicht. Und noch schlimmer: PyPy wird oft als "Python in Python" bezeichnet, außer es gibt tatsächlich keine einzige Python-Zeile darin. Das Ganze ist in RPython geschrieben, das Python-Programmierern vertraut ist, aber nicht Python . Ebenso SqueakVM: Es ist nicht in Smalltalk geschrieben, es ...
Jörg W Mittag

… Ist in Slang geschrieben, was laut denjenigen, die tatsächlich darin kodiert haben, in seinen Abstraktionsfähigkeiten noch schlimmer ist als C. Der einzige Vorteil, den Slang hat, ist, dass es sich um eine richtige Teilmenge von Smalltalk handelt, was bedeutet, dass Sie eine leistungsstarke Smalltalk-IDE entwickeln (und ausführen und vor allem das Debuggen der VM durchführen können).
Jörg W Mittag

2
Um eine weitere Komplikation hinzuzufügen: Einige interpretierte Sprachen (z. B. FORTH und möglicherweise TeX) können ein ladbares Speicherabbild des laufenden Systems als ausführbare Datei schreiben. In diesem Sinne sind solche Systeme können dann ohne das Original - Interpreter ausführen. Zum Beispiel habe ich einmal einen FORTH-Interpreter geschrieben, indem ich eine 16-Bit-Version von FORTH verwendet habe, um eine 32-Bit-Version für eine andere CPU zu "interpretieren" und auf einem anderen Betriebssystem auszuführen. (Beachten Sie, dass die FORTH-Sprache einen eigenen Assembler enthält, sodass die "FORTH VM" (normalerweise nur 10 oder 20 Maschinencode-Anweisungen) in FORTH selbst geschrieben werden kann.)
alephzero

7

Sie haben Recht, wenn Sie feststellen, dass ein selbsthostender Interpreter immer noch einen Interpreter benötigt, um sich selbst auszuführen, und nicht im gleichen Sinne wie ein Compiler gebootet werden kann.

Eine selbst gehostete Sprache ist jedoch nicht dasselbe wie ein selbst gehosteter Dolmetscher. Das Erstellen eines Interpreters ist normalerweise einfacher als das Erstellen eines Compilers. Um eine neue Sprache zu implementieren, müssen Sie zunächst einen Interpreter in einer anderen Sprache implementieren. Dann können wir diesen Interpreter verwenden, um einen Compiler für unsere Sprache zu entwickeln. Die Sprache wird dann selbst gehostet, da der Compiler interpretiert wird. Der Compiler kann sich dann selbst kompilieren und als vollständig gebootet betrachtet werden.

Ein Sonderfall ist eine selbsthostende JIT-Kompilierungslaufzeit. Es kann mit einem Interpreter in einer Hostsprache beginnen, der dann die neue Sprache verwendet, um die JIT-Kompilierung zu implementieren, wonach sich der JIT-Compiler selbst kompilieren kann. Dies fühlt sich an wie ein sich selbst hostender Dolmetscher, umgeht jedoch das Problem der unendlichen Dolmetscher. Dieser Ansatz wird verwendet, ist aber noch nicht sehr verbreitet.

Eine andere verwandte Technik ist ein erweiterbarer Interpreter, mit dem wir Erweiterungen in der zu interpretierenden Sprache erstellen können. Zum Beispiel könnten wir neue Opcodes in der Sprache implementieren. Dies kann einen Bare-Bones-Interpreter in einen funktionsreichen Interpreter verwandeln, solange wir zirkuläre Abhängigkeiten vermeiden.

Ein Fall, der tatsächlich ziemlich häufig auftritt, ist die Fähigkeit der Sprache, ihre eigene Analyse zu beeinflussen, z. B. als Makros, die zur Analysezeit ausgewertet werden. Da die Makrosprache mit der verarbeiteten Sprache identisch ist, ist sie in der Regel wesentlich funktionsreicher als dedizierte oder eingeschränkte Makrosprachen. Es ist jedoch richtig zu beachten, dass die Sprache, in der die Erweiterung ausgeführt wird, sich geringfügig von der Sprache nach der Erweiterung unterscheidet.

Wenn „echte“ selbst gehostete Dolmetscher verwendet werden, geschieht dies normalerweise aus Bildungs- oder Forschungsgründen. Beispielsweise ist die Implementierung eines Interpreters für Scheme inside Scheme eine coole Methode, um Programmiersprachen zu unterrichten (siehe SICP).

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.