Czy tłumacz ustny wytwarza kod maszynowy?

42

Intensywnie studiuję tematy kompilatorów i tłumaczy. Chcę sprawdzić, czy moje podstawowe zrozumienie jest prawidłowe, więc załóżmy, że:

Mam język o nazwie „Foobish”, a jego słowami kluczowymi są

<OUTPUT> 'TEXT', <Number_of_Repeats>;

Więc jeśli chcę drukować na konsoli 10 razy, napiszę

OUTPUT 'Hello World', 10;

Witaj plik World.foobish.

Teraz piszę tłumacza w wybranym przeze mnie języku - C # w tym przypadku:

using System;

namespace FoobishInterpreter
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            analyseAndTokenize(Hello World.foobish-file)//Pseudocode
            int repeats = Token[1];
            string outputString = Token[0];
            for (var i = 0; i < repeats; i++)
            {
                Console.WriteLine(outputString);
            }
        }
    }
}

Na bardzo łatwym poziomie interpretera, tłumacz analizowałby plik skryptu itp. I wykonywał foobish-język na drodze implementacji interpretera.

Czy kompilator stworzyłby język maszynowy działający bezpośrednio na fizycznym sprzęcie?

Czyli interpreter nie tworzy języka maszynowego, ale czy kompilator robi to dla swoich danych wejściowych?

Czy mam jakieś nieporozumienia w podstawowy sposób działania kompilatorów i tłumaczy?

Szary Lis
źródło
21
Jak myślisz, co robi kompilator C #? Podpowiedź nie generuje kodu maszynowego.
Philip Kendall
3
Kompilator Java tworzy kod dla JVM. Tak więc maszyną docelową kompilatora może być maszyna wirtualna, która nie jest wykonywana bezpośrednio przez sprzęt. Główną różnicą między tłumaczem a kompilatorem jest to, że kompilator najpierw sprawdza i tłumaczy cały kod źródłowy na docelowy język maszynowy. Ten skompilowany kod jest następnie wykonywany przez maszynę, dla której był przeznaczony. Z drugiej strony tłumacz będzie tłumaczył i wykonywał fragmenty twojego programu w locie.
Giorgio
@Giorgio: Masz na myśli JIT?
Robert Harvey
2
@RobertHarvey: Miałem na myśli kompilator Java (javac): o ile wiem, tworzy kod bajtowy dla JVM. I znowu AFAIK, JIT później (w czasie wykonywania) kompiluje kod bajtowy, który jest bardzo często używany w natywnym języku maszynowym.
Giorgio
4
kompilator oznacza tłumaczenie. Może emitować wszystkie rodzaje języków: c, asembler, javascript, kod maszynowy.
Esben Skov Pedersen

Odpowiedzi:

77

Terminy „interpreter” i „kompilator” są znacznie bardziej rozmyte niż kiedyś. Wiele lat temu kompilatory częściej produkowały kod maszynowy do późniejszego wykonania, a tłumacze mniej więcej „wykonywali” kod źródłowy. Te dwa terminy były wtedy dobrze zrozumiane.

Ale dziś istnieje wiele odmian używania „kompilatora” i „interpretera”. Na przykład VB6 „kompiluje” bajtowy kod (forma języka pośredniego ), który jest następnie „interpretowany” przez środowisko wykonawcze VB. Podobny proces zachodzi w języku C #, który wytwarza plik CIL, który jest następnie wykonywany przez kompilator Just-In-Time (JIT), który w dawnych czasach byłby uważany za tłumacza. Możesz „zamrozić” dane wyjściowe JIT do rzeczywistego pliku binarnego za pomocą NGen.exe , którego produkt byłby w przeszłości kompilatorem .

Odpowiedź na twoje pytanie nie jest już tak prosta, jak kiedyś.

Dalsze czytanie
Kompilatory kontra tłumacze na Wikipedii

Robert Harvey
źródło
6
@Giorgio: Większość tłumaczy obecnie nie wykonuje kodu źródłowego, a raczej dane wyjściowe AST lub czegoś podobnego. Kompilatory mają podobny proces. To rozróżnienie nie jest tak wyraźne, jak ci się wydaje.
Robert Harvey
5
„Możesz„ zamrozić ”dane wyjściowe JIT do rzeczywistego pliku binarnego za pomocą NGen.exe, którego produkt byłby wynikiem kompilatora w dawnych czasach.”: Ale nadal jest to wynik kompilatora (czyli kompilatora just-in-time). Nie ma znaczenia, kiedy kompilator jest uruchomiony, ale co robi. Kompilator pobiera jako dane wejściowe reprezentację fragmentu kodu i generuje nową reprezentację. Tłumacz wyświetli wynik wykonania tego fragmentu kodu. Są to dwa różne procesy, bez względu na to, jak je wymieszasz i kiedy to wykonasz.
Giorgio
4
„Kompilator” to po prostu termin, który postanowili dołączyć do GCC. Zdecydowali się nie nazywać NGen kompilatorem, nawet jeśli tworzy kod maszynowy, woląc zamiast tego dołączyć ten termin do poprzedniego kroku, który można by nazwać tłumaczem, nawet jeśli tworzy on kod maszynowy (niektórzy tłumacze również to robią). Chodzi mi o to, że w dzisiejszych czasach nie ma wiążącej zasady, którą można by przywołać, aby definitywnie nazwać coś kompilatorem lub tłumaczem, poza tym, że „zawsze tak to nazywali”.
Robert Harvey
4
O ile bardzo rozumiem, w dzisiejszych czasach procesory x86 są w połowie drogi do bycia sprzętowymi silnikami JIT, a zespół ma coraz mniejszą zależność od tego, co dokładnie zostanie wykonane.
Leushenko
4
@RobertHarvey, podczas gdy zgadzam się, że nie ma wyraźnej linii podziału między technikami stosowanymi w interprecie i kompilatorze, istnieje dość wyraźny podział funkcji: jeśli wynikiem wykonania danego narzędzia z kodem programu jako danymi wejściowymi jest wykonanie tego program, narzędzie jest tłumaczem. Jeśli wynikiem jest wynik tłumaczenia programu na mniej abstrakcyjną formę, jest to kompilator. Jeśli wynikiem jest tłumaczenie na bardziej abstrakcyjną formę, jest to dekompilator. Przypadki, w których więcej niż jeden z tych wyników są niejednoznaczne.
Jules
34

Podsumowanie, które podaję poniżej, oparte jest na „Kompilatorach, zasadach, technikach i narzędziach”, Aho, Lam, Sethi, Ullman, (Pearson International Edition, 2007), strony 1, 2, z dodatkiem moich własnych pomysłów.

Dwa podstawowe mechanizmy przetwarzania programu to kompilacja i interpretacja .

Kompilacja pobiera jako dane wejściowe program źródłowy w danym języku i generuje program docelowy w języku docelowym.

source program --> | compiler | --> target program

Jeśli językiem docelowym jest kod maszynowy, można go wykonać bezpośrednio na niektórych procesorach:

input --> | target program | --> output

Kompilacja obejmuje skanowanie i tłumaczenie całego programu wejściowego (lub modułu) i nie wymaga jego wykonania.

Interpretacja przyjmuje jako dane wejściowe program źródłowy i jego dane wejściowe, i tworzy wynik programu źródłowego

source program, input --> | interpreter | --> output

Interpretacja zazwyczaj polega na przetwarzaniu (analizie i wykonywaniu) programu pojedynczo.

W praktyce wiele procesorów językowych stosuje połączenie obu tych podejść. Np. Programy Java są najpierw tłumaczone (kompilowane) na program pośredni (kod bajtowy):

source program --> | translator | --> intermediate program

dane wyjściowe z tego kroku są następnie wykonywane (interpretowane) przez maszynę wirtualną:

intermediate program + input --> | virtual machine | --> output

Aby jeszcze bardziej skomplikować sytuację, JVM może wykonać kompilację just-in-time w czasie wykonywania, aby przekonwertować kod bajtowy na inny format, który jest następnie wykonywany.

Ponadto, nawet podczas kompilacji do języka maszynowego, plik binarny jest uruchamiany przez interpreter, który jest implementowany przez procesor. Dlatego nawet w tym przypadku używasz hybrydy kompilacji + interpretacji.

Tak więc rzeczywiste systemy używają mieszanki tych dwóch, więc trudno powiedzieć, czy dany procesor językowy jest kompilatorem, czy tłumaczem, ponieważ prawdopodobnie użyje obu mechanizmów na różnych etapach przetwarzania. W takim przypadku prawdopodobnie bardziej odpowiednie byłoby użycie innego, bardziej neutralnego terminu.

Niemniej kompilacja i interpretacja to dwa odrębne rodzaje przetwarzania, jak opisano na powyższych schematach,

Aby odpowiedzieć na wstępne pytania.

Kompilator stworzyłby język maszynowy, który działa bezpośrednio na fizycznym sprzęcie?

Kompilator niekoniecznie tłumaczy program napisany dla maszyny M1 na równoważny program napisany dla maszyny M2. Maszyna docelowa może być zaimplementowana sprzętowo lub być maszyną wirtualną. Koncepcyjnie nie ma różnicy. Ważne jest to, że kompilator patrzy na fragment kodu i tłumaczy go na inny język bez wykonywania go.

Więc interpreter nie tworzy języka maszynowego, ale kompilator robi to dla swojego wejścia?

Jeśli podczas tworzenia odwołujesz się do wyniku, kompilator tworzy program docelowy, który może być w języku maszynowym, interpreter tego nie robi.

Giorgio
źródło
7
Innymi słowy: interpreter pobiera program P i wytwarza swoje wyjście O, kompilator pobiera P i wytwarza program P ', który wypisuje O; interpretatory często zawierają komponenty, które są kompilatorami (np. do kodu bajtowego, reprezentacji pośredniej lub instrukcji maszynowych JIT) i podobnie kompilator może zawierać interpreter (np. do oceny obliczeń w czasie kompilacji).
Jon Purdy,
„kompilator może zawierać interpreter (np. do oceny obliczeń w czasie kompilacji)”: Dobra uwaga. Sądzę, że makra Lisp i szablony C ++ mogą być wstępnie przetworzone w ten sposób.
Giorgio
Jeszcze prościej, preprocesor C kompiluje kod źródłowy C z dyrektywami CPP do zwykłego C i zawiera interpreter wyrażeń boolowskich takich jak defined A && !defined B.
Jon Purdy
@JonPurdy Zgodziłbym się z tym, ale dodałbym także klasę „tradycyjnych tłumaczy”, która nie korzysta z pośrednich reprezentacji poza być może tokenizowaną wersją źródła. Przykładami byłyby powłoki, wiele BASIC-ów, klasyczny Lisp, Tcl przed 8.0 i bc.
hobbs
1
@naxa - patrz odpowiedź Lawrence'a i komentarze Paula Drapera na temat typów kompilatorów. Asembler to specjalny rodzaj kompilatora, w którym (1) język wyjściowy jest przeznaczony do bezpośredniego wykonania przez maszynę lub maszynę wirtualną oraz (2) istnieje bardzo prosta korespondencja jeden do jednego między instrukcjami wejściowymi a instrukcjami wyjściowymi.
Jules
22

Kompilator stworzyłby język maszynowy

Nie Kompilator jest po prostu program, który bierze za swoje wejście program napisany w języku A i generuje jako wyjście do semantycznie równoważne programu w języku B . Język B może być dowolny, nie musi to być język maszynowy.

Kompilator może kompilować z języka wysokiego poziomu na inny język wysokiego poziomu (np. GWT, który kompiluje Javę do ECMAScript), z języka wysokiego poziomu na język niskiego poziomu (np. Gambit, który kompiluje Scheme to C), od języka wysokiego poziomu do kodu maszynowego (np. GCJ, który kompiluje Javę do kodu natywnego), od języka niskiego poziomu do języka wysokiego poziomu (np. Clue, który kompiluje C do Java, Lua, Perl, ECMAScript i Common Lisp), od języka niskiego poziomu do innego języka niskiego poziomu (np. Android SDK, który kompiluje kod bajtowy JVML do kodu bajtowego Dalvik), od języka niskiego poziomu do kodu maszynowego (np. Kompilator C1X, który jest częścią HotSpot, który kompiluje kod bajtowy JVML do kodu maszynowego, kod maszynowy do języka wysokiego poziomu (dowolny tak zwany „dekompilator”, także Emscripten, który kompiluje kod maszynowy LLVM do ECMAScript),kod maszynowy do języka niskiego poziomu (np. kompilator JIT w JPC, który kompiluje kod natywny x86 do kodu bajtowego JVML) i kod rodzimy do kodu natywnego (np. kompilator JIT w PearPC, który kompiluje kod natywny PowerPC do kodu natywnego x86).

Zauważ też, że „kod maszynowy” jest bardzo rozmytym terminem z kilku powodów. Na przykład istnieją procesory, które natywnie wykonują kod bajtowy JVM, i istnieją interpretery programowe dla kodu maszynowego x86. Co zatem sprawia, że ​​jeden „natywny kod maszynowy” jest inny, a drugi nie? Ponadto każdy język jest kodem abstrakcyjnej maszyny dla tego języka.

Istnieje wiele specjalistycznych nazw dla kompilatorów wykonujących funkcje specjalne. Pomimo tego, że są to wyspecjalizowane nazwy, wszystkie z nich są nadal kompilatorami, tylko specjalnymi rodzajami kompilatorów:

  • jeśli język A jest postrzegany jako mniej więcej taki sam poziom abstrakcji jak język B , kompilator można nazwać transpilatorem (np. transpilatorem Ruby-do-ECMAScript lub transpilatorem ECMAScript2015-do-ECMAScript5)
  • jeśli język A jest postrzegany jako niższy poziom abstrakcji niż język B , kompilator można nazwać dekompilatorem (np. dekompilator x86-kod-maszynowy-do-C)
  • jeśli język == język B , kompilator może być nazywany optymalizator , obfuscator lub minifier (w zależności od konkretnej funkcji kompilator)

który działa bezpośrednio na fizycznym sprzęcie?

Niekoniecznie. Może być uruchomiony w tłumaczu lub na maszynie wirtualnej. Można go dodatkowo skompilować w innym języku.

Więc interpreter nie tworzy języka maszynowego, ale kompilator robi to dla swojego wejścia?

Tłumacz nic nie produkuje. Po prostu uruchamia program.

Kompilator coś wytwarza, ale niekoniecznie musi to być język maszynowy, może to być dowolny język. Może to być nawet ten sam język, co język wprowadzania! Na przykład Superkompilatory, LLC ma kompilator, który przyjmuje Java jako dane wejściowe i produkuje zoptymalizowaną Javę jako dane wyjściowe. Istnieje wiele kompilatorów ECMAScript, które pobierają ECMAScript jako dane wejściowe i produkują zoptymalizowany, zminimalizowany i zaciemniony ECMAScript.


Możesz być zainteresowanym także tym:

Jörg W Mittag
źródło
16

Myślę, że powinieneś całkowicie porzucić pojęcie „kompilator kontra tłumacz”, ponieważ jest to fałszywa dychotomia.

  • Kompilator jest transformator : Zmienia program komputerowy napisany w języku źródłowym i wyprowadza odpowiednik w języku docelowym . Zwykle język źródłowy jest na wyższym poziomie niż język docelowy - a jeśli jest odwrotnie, często nazywamy ten transformator dekompilatorem .
  • Interpreter jest silnik wykonanie . Wykonuje program komputerowy napisany w jednym języku, zgodnie ze specyfikacją tego języka. Najczęściej używamy tego terminu dla oprogramowania (ale w pewnym sensie klasyczny procesor może być postrzegany jako sprzętowy „tłumacz” dla jego kodu maszynowego).

Wspólne słowo określające użyteczność abstrakcyjnego języka programowania w świecie rzeczywistym to implementacja .

W przeszłości, implementacja języka programowania często składały się tylko kompilator (i CPU to wygenerowany kod) lub po prostu tłumacza - więc może nie wyglądał jak tych dwóch rodzajów narzędzi wzajemnie się wykluczają. Dzisiaj widać wyraźnie, że tak nie jest (i nigdy nie było tak na początku). Podjęcie zaawansowanej implementacji języka programowania i próba przekazania mu nazwy „kompilator” lub „tłumacz” często prowadzi do niejednoznacznych lub niekonsekwentnych rezultatów.

Jedna implementacja języka programowania może obejmować dowolną liczbę kompilatorów i tłumaczy , często w wielu postaciach (samodzielna, w locie), dowolną liczbę innych narzędzi, takich jak analizatory statyczne i optymalizatory , oraz dowolną liczbę kroków. Może nawet obejmować całe implementacje dowolnej liczby języków pośrednich (które mogą być niezwiązane z tym, który jest wdrażany).

Przykłady schematów wdrażania obejmują:

  • Kompilator prądu przemiennego, który przekształca C na kod maszynowy x86 oraz procesor x86, który wykonuje ten kod.
  • Kompilator AC, który przekształca C na LLVM IR, kompilator zaplecza LLVM, który przekształca LLVM IR na kod maszynowy x86 oraz procesor x86, który wykonuje ten kod.
  • Kompilator AC, który przekształca C na LLVM IR oraz interpreter LLVM, który wykonuje LLVM IR.
  • Kompilator Java, który przekształca Javę na kod bajtowy JVM, oraz JRE z interpreterem, który wykonuje ten kod.
  • Kompilator Java, który przekształca Javę na kod bajtowy JVM, oraz JRE z interpreterem wykonującym niektóre części tego kodu i kompilatorem, który przekształca inne części tego kodu na kod maszynowy x86, oraz procesorem x86, który wykonuje ten kod.
  • Kompilator Java, który przekształca Javę na kod bajtowy JVM, oraz procesor ARM, który wykonuje ten kod.
  • Kompilator AC # przekształcający C # na CIL, CLR z kompilatorem przekształcającym CIL na kod maszynowy x86 oraz procesor x86 wykonujący ten kod.
  • Tłumacz języka Ruby, który wykonuje Ruby.
  • Środowisko Ruby z interpreterem wykonującym Ruby i kompilatorem, który przekształca Ruby na kod maszynowy x86, oraz procesorem x86, który wykonuje ten kod.

...i tak dalej.

Theodoros Chatzigiannakis
źródło
+1 za wskazanie, że nawet kodowania zaprojektowane dla reprezentacji pośredniej (np. Kod bajtowy java) mogą mieć implementacje sprzętowe.
Jules
7

Podczas gdy linie między kompilatorami i tłumaczami z czasem się rozmyły, nadal można rysować linię między nimi, patrząc na semantykę tego, co program powinien zrobić i co robi kompilator / tłumacz.

Kompilator wygeneruje inny program (zwykle w języku niższego poziomu, takim jak kod maszynowy), który, jeśli ten program zostanie uruchomiony, zrobi to, co powinien zrobić Twój program.

Tłumacz zrobi to, co powinien zrobić twój program.

Dzięki tym definicjom miejsca, w których robi się rozmyte, to przypadki, w których można uznać, że Twój kompilator / interpreter robi różne rzeczy w zależności od tego, jak na to patrzysz. Na przykład Python pobiera kod Pythona i kompiluje go do skompilowanego kodu bajtowego Pythona. Jeśli ten kod bajtowy Pythona jest uruchamiany przez interpreter kodu bajtowego Python , robi to, co powinien zrobić Twój program. Jednak w większości sytuacji programiści Pythona myślą, że oba te kroki są wykonywane w jednym dużym kroku, więc wybierają interpreter CPython jako interpretację swojego kodu źródłowego, a fakt, że został skompilowany po drodze, jest uważany za szczegół implementacji . W ten sposób wszystko zależy od perspektywy.

Cort Ammon
źródło
5

Oto prosta konceptualna niejednoznaczność między kompilatorami a tłumaczami.

Rozważ 3 języki: język programowania , P (w jakim program jest napisany); język domeny , D (dla tego, co dzieje się z uruchomionym programem); i język docelowy , T (jakiś trzeci język).

Koncepcyjnie

  • kompilator tłumaczy P do T, dzięki czemu można ocenić T (d); natomiast

  • interpreter ocenia P (D) bezpośrednio.

Lawrence
źródło
1
Większość współczesnych tłumaczy faktycznie nie ocenia bezpośrednio języka źródłowego, ale raczej pośrednią reprezentację języka źródłowego.
Robert Harvey
4
@RobertHarvey To nie zmienia koncepcyjnego rozróżnienia między terminami.
Lawrence
1
To, co tak naprawdę nazywasz tłumaczem, to część, która ocenia reprezentację pośrednią. Z definicji tworzona jest reprezentacja pośrednia. Jest to kompilator .
Robert Harvey
6
@RobertHarvey Nie bardzo. Warunki zależą od poziomu abstrakcji, nad którym pracujesz. Jeśli spojrzysz pod spodem, narzędzie może robić wszystko. Analogicznie powiedz, że jedziesz do obcego kraju i zabierasz ze sobą dwujęzycznego przyjaciela Boba. Jeśli komunikujesz się z miejscowymi rozmawiając z Bobem, który z kolei rozmawia z miejscowymi, Bob działa jako tłumacz ustny (nawet jeśli przed rozmową pisze coś w ich języku). Jeśli poprosisz Boba o frazy, a Bob napisze je w obcym języku, a komunikujesz się z miejscowymi, odwołując się do tych pism (nie Bob), Bob będzie dla ciebie kompilatorem.
Lawrence
1
Doskonała odpowiedź. Warto zauważyć: w dzisiejszych czasach możesz usłyszeć „transpiler”. Jest to kompilator, w którym P i T są podobnymi poziomami abstrakcji, dla niektórych definicji podobnych. (Np. Transpiler ES5 na ES6).
Paul Draper,