Czy możliwe jest utworzenie tłumacza „bootstrapped” niezależnego od oryginalnego tłumacza?

21

Według Wikipedii termin „bootstrapping” w kontekście pisania kompilatorów oznacza :

W informatyce ładowanie to proces pisania kompilatora (lub asemblera) w źródłowym języku programowania, który zamierza skompilować. Zastosowanie tej techniki prowadzi do kompilatora samoobsługowego.

I rozumiem, jak to by działało. Ta historia wydaje się jednak nieco inna dla tłumaczy. Teraz oczywiście można napisać tłumacza na własny hosting. Nie o to pytam. Co ja właściwie pytaniem jest: Czy to możliwe, aby self-hosted interpretera niezależnie od oryginału, pierwszego tłumacza . Aby wyjaśnić, co mam na myśli, rozważ ten przykład:

Piszesz swoją pierwszą wersję tłumacza języka X oraz interpreter dla nowego języka, który tworzysz, zwany Y . Najpierw używasz kompilatora języka X , aby utworzyć plik wykonywalny. Teraz można interpretować plików napisanych w nowym języku Y pomocą tłumacza w języku X .

Teraz, o ile mi zrozumieć, aby móc „bootstrap” tłumacza, który napisałeś w języku X , trzeba by przepisać tłumacza w języku Y . Ale tu jest haczyk: nawet jeśli nie przepisać całą tłumacza w języku Y , jesteś nadal będzie potrzebował oryginał interpreter języka napisałeś w X . Ponieważ aby uruchomić interpreter w języku Y , musisz zinterpretować pliki źródłowe. Ale co dokładnie będzie interpretować pliki źródłowe? Oczywiście nie może to być niczym, więc musisz nadal korzystać z pierwszego tłumacza.

Bez względu na to, ilu nowych tłumaczy piszesz w języku Y , zawsze będziesz musiał użyć pierwszego tłumacza napisanego w X do tłumaczenia kolejnych tłumaczy. Wydaje się, że jest to problem tylko ze względu na naturę tłumaczy.

Jednak z drugiej strony, ten artykuł Wikipedii na temat tłumaczy faktycznie mówi o tłumaczach z własnym hostingiem . Oto mały fragment, który jest istotny:

Tłumacz ustny to tłumacz języka programowania napisany w języku programowania, który może się interpretować; przykładem jest interpreter języka BASIC napisany w języku BASIC. Interpretatory są powiązane z kompilatorami hostującymi.

Jeśli nie ma kompilatora dla języka, który ma być interpretowany, utworzenie własnego interpretera wymaga implementacji języka w języku hosta (który może być innym językiem programowania lub asemblerem). Dzięki posiadaniu pierwszego takiego tłumacza system jest ładowany i nowe wersje interpretera mogą być opracowywane w samym języku

Jednak nadal nie jest dla mnie jasne, jak dokładnie to zostanie zrobione. Wydaje się, że bez względu na wszystko, zawsze będziesz zmuszony użyć pierwszej wersji tłumacza napisanej w języku hosta.

Teraz wspomniany wyżej artykuł prowadzi do innego artykułu, w którym Wikipedia podaje przykłady domniemanych tłumaczy hostingowych . Po bliższym przyjrzeniu się wydaje się, że główna część „tłumaczeń ustnych” wielu tłumaczy z własnego hostingu (zwłaszcza niektórych z bardziej powszechnych, takich jak PyPy lub Rubinius), są w rzeczywistości napisane w innych językach, takich jak C ++ lub C.

Czy to, co opisałem powyżej, jest możliwe? Czy tłumacz na własnym serwerze może być niezależny od swojego oryginalnego hosta? Jeśli tak, to jak dokładnie to zrobić?

Christian Dean
źródło

Odpowiedzi:

24

Krótka odpowiedź brzmi: masz rację, zawsze potrzebujesz innego tłumacza napisanego w X lub kompilatora z Y na inny język, dla którego już masz tłumacza. Tłumacze wykonują, kompilatory tłumaczą tylko z jednego języka na inny, w pewnym momencie w twoim systemie musi być tłumacz… nawet to jest tylko procesor.

Bez względu na to, ilu nowych tłumaczy piszesz w języku Y , zawsze będziesz musiał użyć pierwszego tłumacza napisanego w X do tłumaczenia kolejnych tłumaczy. Wydaje się, że jest to problem tylko ze względu na naturę tłumaczy.

Poprawny. Co można zrobić, to napisać kompilator od Y do X (lub inny język, dla którego trzeba tłumacza), a można nawet zrobić w Y . Następnie możesz uruchomić kompilator Y napisany w Y na interprecie Y napisanym w X (lub na Y interpreter napisany w Y, uruchomiony na interpreter Y napisany w X lub na interpreter Y napisany w Y, uruchomiony na interpreter Y napisany w Y działa na Yinterpreter napisany w X lub… ad infinitum) w celu skompilowania interpretera Y napisanego w Y do X , abyś mógł go następnie wykonać na interpreterze X. W ten sposób pozbyłeś się swojego interpretera Y napisanego w X , ale teraz potrzebujesz interpretera X (wiemy, że już go mamy, ponieważ inaczej nie moglibyśmy uruchomić interpretera X napisanego w Y ), a ty najpierw musiał napisać kompilator Y - to - X .

Jednak z drugiej strony, artykuł Wikipedii na temat tłumaczy faktycznie mówi o tłumaczach z własnym hostingiem. Oto mały fragment, który jest istotny:

Tłumacz ustny to tłumacz języka programowania napisany w języku programowania, który może się interpretować; przykładem jest interpreter języka BASIC napisany w języku BASIC. Interpretatory są powiązane z kompilatorami hostującymi.

Jeśli nie ma kompilatora dla języka, który ma być interpretowany, utworzenie własnego interpretera wymaga implementacji języka w języku hosta (który może być innym językiem programowania lub asemblerem). Dzięki posiadaniu pierwszego takiego tłumacza system jest ładowany i nowe wersje interpretera mogą być opracowywane w samym języku

Jednak nadal nie jest dla mnie jasne, jak dokładnie to zostanie zrobione. Wydaje się, że bez względu na wszystko, zawsze będziesz zmuszony użyć pierwszej wersji tłumacza napisanej w języku hosta.

Poprawny. Pamiętaj, że artykuł w Wikipedii wyraźnie mówi, że potrzebujesz drugiej implementacji swojego języka i nie mówi, że możesz się go pozbyć.

Teraz wspomniany wyżej artykuł prowadzi do innego artykułu, w którym Wikipedia podaje przykłady domniemanych tłumaczy hostingowych. Po bliższym przyjrzeniu się wydaje się, że główna część „tłumaczeń ustnych” wielu tłumaczy z własnego hostingu (zwłaszcza niektórych z bardziej powszechnych, takich jak PyPy lub Rubinius), są w rzeczywistości napisane w innych językach, takich jak C ++ lub C.

Znowu poprawne. To są naprawdę złe przykłady. Weźmy na przykład Rubiniusa. Tak, to prawda, że ​​rubinowa część Rubiniusa jest hostowana przez siebie, ale jest to kompilator, a nie interpreter: kompiluje się do kodu źródłowego Ruby do kodu bajtowego Rubiniusa. Część OTOH tłumacza nie jest hostowana: interpretuje kod bajtowy Rubiniusa, ale jest napisany w C ++. Tak więc nazywanie Rubiniusa „tłumaczem na własnym serwerze” jest błędne: część hostowana nie jest tłumaczem , a część tłumacza nie jest hostowana na własną rękę .

PyPy jest podobny, ale jeszcze bardziej niepoprawny: nie jest nawet napisany w Pythonie, jest napisany w RPython, który jest innym językiem. Jest składniowo podobny do Pythona, semantycznie „rozszerzonym podzbiorem”, ale w rzeczywistości jest to język o typie statycznym z grubsza na tym samym poziomie abstrakcji co Java, a jego implementacją jest kompilator z wieloma zapleczami, który kompiluje kod źródłowy RPython do C, ECMAScript kod źródłowy, kod bajtu CIL, kod bajtowy JVM lub kod źródłowy Pythona.

Czy to, co opisałem powyżej, jest możliwe? Czy tłumacz-tłumacz może być niezależny od swojego oryginalnego hosta? Jeśli tak, to jak dokładnie to zrobić?

Nie, nie na własną rękę. Musisz zachować oryginalnego tłumacza lub napisać kompilator i skompilować własnego interpretera.

Tam niektóre meta-okrągły maszyn wirtualnych, takich jak Klein (napisany w Jaźni ) i Maxine (napisany w Javie). Zauważ jednak, że tutaj definicja „meta-okólnika” jest jeszcze inna: te maszyny wirtualne nie są napisane w języku, który wykonują: Klein wykonuje własny kod bajtowy, ale jest napisany w Self, Maxine wykonuje kod bajtowy JVM, ale jest napisany w Javie. Jednak kod źródłowy Własna / Java VM faktycznie pobiera skompilowany do self / JVM kodu bajtowego, a następnie wykonywany przez maszynę wirtualną, więc do czasu VM zostanie wykonany, to jest w języku to wykonuje. Uff

Zauważ też, że różni się to od maszyn wirtualnych, takich jak SqueakVM i Jikes RVM . Jikes jest napisany w Javie, a SqueakVM jest napisany w Slangu (statycznie typowany składniowy i semantyczny podzbiór Smalltalk z grubsza na tym samym poziomie abstrakcji, co asembler wysokiego poziomu), i oba są statycznie kompilowane do kodu natywnego przed ich uruchomieniem. Nie biegają w sobie. Państwo może jednak uruchomić je na górze siebie (lub na górze innego Smalltalk VM / JVM). W tym sensie nie jest to jednak „meta-okrągły”.

Maxine i Klein, OTOH zróbwbiegać w siebie; wykonują swój własny kod bajtowy przy użyciu własnej implementacji. To naprawdę zaskakujące! Daje to kilka fajnych możliwości optymalizacji, na przykład ponieważ maszyna wirtualna wykonuje się sama z programem użytkownika, może wstawiać połączenia z programu użytkownika do maszyny wirtualnej i odwrotnie, np. Wywoływanie śmietnika lub alokatora pamięci można wprowadzić w użytkownika kod i odblaskowe wywołania zwrotne w kodzie użytkownika można wstawić do maszyny wirtualnej. Ponadto, wszystkie sprytne sztuczki optymalizacyjne, jakie wykonują współczesne maszyny wirtualne, polegające na obserwowaniu wykonującego programu i optymalizowaniu go w zależności od rzeczywistego obciążenia i danych, maszyna wirtualna może zastosować te same sztuczki do siebie podczas wykonywania programu użytkownika podczas działania programu użytkownika wykonuje określone obciążenie. Innymi słowy, VM bardzo się w tym specjalizujeokreślony program uruchamiający to obciążenie.

Zauważ jednak, że omijałem użycie słowa „tłumacz” powyżej i zawsze używałem słowa „wykonać”? Cóż, te maszyny wirtualne nie są zbudowane wokół interpreterów, są oparte na kompilatorach (JIT). Do Maxine został dodany interpreter później, ale zawsze potrzebujesz kompilatora: musisz uruchomić maszynę wirtualną raz na innej maszynie wirtualnej (np. Oracle HotSpot w przypadku Maxine), aby maszyna wirtualna mogła (JIT) się skompilować. W przypadku Maxine JIT skompiluje własną fazę rozruchu, a następnie serializuje ten skompilowany kod natywny do obrazu VM bootstrap i przyklei bardzo prosty bootloader z przodu (jedyny składnik VM napisany w C, chociaż to tylko dla wygody , może być również w Javie). Teraz możesz użyć Maxine do samodzielnego wykonania.

Jörg W Mittag
źródło
Tak . Nigdy nie wiedziałem, że świat tłumaczy hostingowych jest tak lepki! Dzięki za dobry przegląd.
Christian Dean
1
Haha, dlaczego świat miałby być mniej wymagający od koncepcji? ;-)
Jörg W Mittag
3
Myślę, że jednym z problemów jest to, że ludzie często bawią się szybko i swobodnie językami. Na przykład Rubinius jest zwykle nazywany „Rubinem w Rubinie”, ale to tylko połowa historii. Tak, ściśle mówiąc , kompilator Ruby w Rubinius jest napisany w Rubim, ale VM, która wykonuje kod bajtowy, nie jest. Co gorsza: PyPy jest często nazywany „Python w Pythonie”, z tym wyjątkiem, że w rzeczywistości nie ma w nim ani jednej linii Pythona. Całość została napisana w języku RPython, który został zaprojektowany tak, aby był znany programistom Python, ale nie jest Pythonem . Podobnie SqueakVM: nie jest napisane w Smalltalk, to…
Jörg W Mittag
… Jest napisane w języku Slang, który według ludzi, którzy go w rzeczywistości zakodowali, ma jeszcze gorsze możliwości w zakresie abstrakcji niż C. Jedyną zaletą Slanga jest to, że jest to odpowiedni podzbiór Smalltalk, co oznacza, że ​​możesz go rozwinąć (oraz uruchomić i, co najważniejsze, debugować maszynę wirtualną) na potężnym IDE Smalltalk.
Jörg W Mittag
2
Aby dodać kolejną komplikację: niektóre interpretowane języki (np. FORTH i ewentualnie TeX) są w stanie zapisać ładowalny obraz pamięci działającego systemu, jako plik wykonywalny. W tym sensie takie systemy mogą następnie działać bez oryginalnego tłumacza. Na przykład napisałem kiedyś interpreter FORTH, używając 16-bitowej wersji FORTH, aby „zinterpretować” 32-bitową wersję dla innego procesora i uruchomić na innym systemie operacyjnym. (Uwaga: język FORTH zawiera własny asembler, więc „FORTH VM” (która zwykle zawiera tylko 10 lub 20 instrukcji kodu maszynowego) można napisać w samym FORTH.)
alephzero
7

Masz rację, zauważając, że interpreter samoobsługowy nadal wymaga interpretera do uruchomienia się i nie można go uruchomić w tym samym sensie co kompilator.

Jednak język hostowany samodzielnie to nie to samo, co tłumacz hostowany przez siebie. Zwykle łatwiej jest zbudować interpreter niż kompilator. Dlatego, aby wdrożyć nowy język, możemy najpierw zaimplementować tłumacza w niezwiązanym języku. Następnie możemy użyć tego interpretera do opracowania kompilatora dla naszego języka. Język jest następnie hostowany, ponieważ kompilator jest interpretowany. Kompilator może następnie się skompilować, a następnie można go uznać za w pełni załadowany.

Szczególnym przypadkiem tego jest samoobsługowe środowisko wykonawcze kompilujące JIT. Może zaczynać się od interpretera w języku hosta, który następnie używa nowego języka do implementacji kompilacji JIT, po czym kompilator JIT może się kompilować. To wydaje się być tłumaczem na własnym serwerze, ale omija problem nieskończonych tłumaczy. Takie podejście jest stosowane, ale nie jest jeszcze zbyt powszechne.

Inną powiązaną techniką jest rozszerzalny tłumacz, w którym możemy tworzyć rozszerzenia interpretowanego języka. Na przykład możemy zaimplementować nowe kody w języku. Może to zmienić tłumacza gołą kość w bogatego w funkcje tłumacza, o ile unikniemy zależności cyklicznych.

Przypadkiem, który faktycznie występuje dość często, jest zdolność języka do wpływania na jego własną analizę, np. Jako makra oceniane w czasie analizy. Ponieważ język makr jest taki sam, jak język, który jest przetwarzany, jest on zwykle bardziej bogaty w funkcje niż dedykowane lub ograniczone języki makr. Należy jednak zauważyć, że język wykonujący rozszerzenie jest nieco innym językiem niż język po rozszerzeniu.

Gdy używa się „prawdziwych” tłumaczy ustnych, zwykle dzieje się tak ze względu na edukację lub badania. Np. Implementacja interpretera dla Schematu w Scheme to świetny sposób na naukę języków programowania (patrz SICP).

amon
źródło