Czy autorzy kompilatorów rzeczywiście muszą „rozumieć” kod maszynowy? [Zamknięte]

10

To może być dziwne pytanie.

Facet piszący kompilator C ++ (lub inny język inny niż VM): Czy musi umieć czytać / pisać surowy język maszynowy? Jak to działa?

EDYCJA: Mam na myśli szczególnie kompilatory kompilujące się do kodu maszynowego, a nie jakiegoś innego języka programowania.

Aviv Cohn
źródło
1
Nie. Nie musisz tego wiedzieć, możesz po prostu ślepo i bezmyślnie skopiować docelową specyfikację ISA: dl.acm.org/citation.cfm?id=1706346
SK-logic
1
Coffescript kompiluje się w javascript.
Kartik
@Kartik Czy kompilator CoffeeScript, który kompiluje się do Javascript, zawiera także kompilator JavaScript, który kompiluje się do dowolnego kompilatora JavaScript? A może kompiluje się tylko do kodu źródłowego Javascript i nic więcej?
Aviv Cohn,
Kompilator coffeescript po prostu konwertuje cofeescript na javascript. JavaScript nie jest kompilowany, jest obsługiwany przez przeglądarkę. Chciałem powiedzieć, że możesz napisać kompilator, który kompiluje jeden język na inny, nie musisz do tego znać języka maszynowego. Innym przykładem jest kompilator „SPL”, który kompiluje gry Shakespeare'a do C ++. shakespearelang.sourceforge.net
Kartik

Odpowiedzi:

15

Nie, wcale nie. Kompilator może zamiast tego emitować kod asemblera (a często nawet preferowany). Asembler zajmuje się następnie tworzeniem rzeczywistego kodu maszynowego.

Nawiasem mówiąc, rozróżnienie implementacji innej niż VM od implementacji VM nie jest przydatne.

  • Po pierwsze, użycie maszyny wirtualnej lub prekompilacji do kodu maszynowego to po prostu różne sposoby implementacji języka; w większości przypadków język można wdrożyć przy użyciu dowolnej strategii. Właściwie musiałem raz użyć interpretera C ++ .

  • Ponadto wiele maszyn wirtualnych, takich jak JVM, ma binarny kod maszynowy i niektóre asemblery, podobnie jak zwykła architektura.

Na szczególną uwagę zasługuje LLVM (używany przez kompilatory Clanga): definiuje maszynę wirtualną, dla której instrukcje mogą być reprezentowane jako kod bajtowy, zestaw tekstowy lub struktura danych, która bardzo łatwo emituje z kompilatora. Więc chociaż byłoby to przydatne do debugowania (i zrozumienia, co robisz), nie musiałbyś nawet wiedzieć o języku asemblera, tylko o API LLVM.

Zaletą LLVM jest to, że jego maszyna wirtualna jest tylko abstrakcją, a kod bajtowy nie jest zwykle interpretowany, ale zamiast tego jest przezroczysty JIT. Jest więc całkowicie możliwe napisanie języka, który jest skutecznie skompilowany, bez konieczności posiadania wiedzy na temat zestawu instrukcji procesora.

amon
źródło
Inną zaletą LLVM jest to, że nie trzeba dogłębnie rozumieć docelowego ISA, aby wdrożyć wydajny backend. Jest dość deklaratywny, więc można prawie skopiować i wkleić specyfikację ISA do plików .td, nawet nie próbując jej zrozumieć.
SK-logic
Dzięki za odpowiedź. Pytanie: Rozumiem z twojej odpowiedzi i odpowiedzi innych, że autor kompilatora nie musi rozumieć kodu maszynowego, może użyć innego narzędzia, które wykonuje dla niego konwersję kodu maszynowego. Jednak facet, który napisał, że narzędzie nie muszą rozumieć język maszynowy, prawda? Facet, który napisał oprogramowanie, które dokonuje faktycznej konwersji z jakiegoś języka na kod maszynowy, musi tak naprawdę rozumieć język maszynowy, prawda?
Aviv Cohn,
5
@Prog tak. Jeśli utworzysz warstwę abstrakcji, musisz tylko zrozumieć warstwę pod sobą. Chociaż dobrze jest mieć podstawową wiedzę o wszystkich warstwach, nie jest to tak naprawdę konieczne. Nie musisz rozumieć fizyki kwantowej, aby używać tranzystora. Aby korzystać z procesora, nie musisz rozumieć konstrukcji układu. Nie musisz znać kodu maszynowego, aby pisać asembler. Nie musisz znać zestawu instrukcji platformy podczas korzystania z maszyny wirtualnej itp. Ale ktoś musiał zbudować te poziomy abstrakcji poniżej twojego.
amon
1
@ SK-logic Nieprawda (przynajmniej jeśli chcesz dobrego kodu) z tego, co słyszałem. Ludzie, którzy zaimplementowali backend Aarch64 dla lvvm, mieli sporo wyzwań (przeniesienie jednego, straszne wzorce przechowywania ładunków, ..). I to nie zważając na największy słoń w pokoju: model pamięci ISA oraz model pamięci języka jesteś zainteresowany Można pracować na kompilator, ale nie można pracować na backend bez zrozumienia architektury ...
Voo
1
Dodam, że koncepcyjnie nie ma istotnej różnicy między asemblerem a kodem maszynowym. Tak naprawdę nie przyniesie ci to wiele korzyści, kiedy już będziesz wiedział, jak użyć konkretnej instrukcji, jaki jest jej kod operacyjny. Jeśli wiesz, jak używać MOV, to nie ma znaczenia, jeśli nie wiesz, że jest to instrukcja 27, która moim zdaniem jest podobna do tego, co opisuje SK-logika.
whatsisname
9

Nie. Kluczową kwestią twojego pytania jest to, że kompilacja jest niezwykle szeroka. Kompilacja może odbywać się z dowolnego języka na dowolny język. A kod asemblera / maszynowy jest tylko jednym z wielu języków docelowych kompilacji. Na przykład języki Java i .NET, takie jak C #, F # i VB.NET, kompilują się do pewnego rodzaju kodu pośredniego zamiast kodu specyficznego dla maszyny. Nie ma znaczenia, czy następnie uruchomi się na maszynie wirtualnej, język jest nadal kompilowany. Istnieje również opcja kompilacji do innego języka, na przykład C. C jest w rzeczywistości dość popularnym celem kompilacji i robi to wiele narzędzi. Na koniec możesz użyć jakiegoś narzędzia lub biblioteki, aby wykonać ciężką pracę przy tworzeniu kodu maszynowego. istnieje na przykład LLVM, który może zmniejszyć wysiłek potrzebny do utworzenia samodzielnego kompilatora.

Ponadto Twoja edycja nie ma żadnego sensu. To jest jak pytanie „Czy każdy inżynier musi zrozumieć, jak działa silnik? A ja pytam o inżynierów pracujących nad silnikami”. Jeśli pracujesz nad programem lub biblioteką, która emituje kod maszynowy, musisz go zrozumieć. Chodzi o to, że nie musisz robić czegoś takiego podczas pisania kompilatora. Wiele osób zrobiło to przed tobą, więc musisz mieć poważny powód, aby to zrobić ponownie.

Euforyk
źródło
A osoba pisząca narzędzie lub bibliotekę, która dokonuje faktycznej konwersji na język maszynowy, musi całkowicie rozumieć język maszynowy, prawda?
Aviv Cohn,
3
@Prog Czy musisz w pełni rozumieć język programowania, aby móc w nim programować? Nie, ale możliwe, że napiszesz kod nieoptymalny i nie możesz robić pewnych rzeczy, które inni mogą być w stanie zrobić. Czy musisz całkowicie rozumieć język maszynowy, jeśli napiszesz kompilator, który to tłumaczy? Nie, ale Twój kompilator będzie nieoptymalny i nie będzie w stanie wykonywać pewnych czynności.
Sumurai8,
@ Sumurai8: chociaż do pewnego stopnia możesz „zrozumieć” język maszynowy fragmentarycznie, aby napisać emiter kodu maszynowego, który lepiej rozumie całość. Na przykład, jeśli napiszesz dobry framework, możesz skonfigurować definicję każdego kodu operacyjnego wraz z jego kosztami i kwestiami związanymi z potokowaniem, a następnie twój framework może napisać zoptymalizowany kod maszynowy, nawet jeśli nie masz doświadczenia w optymalizacji tego konkretnego komputera. Umiejętność samodzielnego zaprogramowania tego kodu maszynowego prawdopodobnie nie zaszkodzi.
Steve Jessop,
@ SteveJessop Jeśli rozumiesz każdy kod do tego stopnia, że ​​możesz nauczyć się, jak łączyć ten kod z innymi kodami, aby wyrazić koncepcję wyższego poziomu, rozumiesz całkowicie język maszyny. Jesteś więc zbyt leniwy, aby znaleźć optymalne rozwiązanie każdego problemu ;-)
Sumurai8
@ Sumurai8: hmm, ale przynajmniej w zasadzie mógłbym „zrozumieć” każdy kod operacji na krótko przez pięć minut, które zajęło mi jego skonfigurowanie, a potem zapomniałem go, zanim „zrozumiem” kod operacji później. Prawdopodobnie nie to rozumie przez pytającego „być w stanie czytać / pisać surowy język maszynowy”. Oczywiście zakładam tutaj całkiem dobrą strukturę, która jest wystarczająco konfigurowalna, aby zdefiniować i wykorzystać wszystkie przydatne informacje o każdym kodzie operacji zestawu instrukcji. LLVM trochę do tego dąży, ale według „Voo” (w komentarzu poniżej) nie trafił.
Steve Jessop,
3

Klasycznie kompilator składa się z trzech części: analizy leksykalnej, analizy i generowania kodu. Analiza leksykalna dzieli tekst programu na słowa kluczowe, nazwy i wartości językowe. Analiza polega na tym, jak tokeny pochodzące z analizy leksykalnej są łączone w poprawne składniowo instrukcje dla języka. Generowanie kodu bierze struktury danych utworzone przez analizator składni i tłumaczy je na kod maszynowy lub inną reprezentację. W dzisiejszych czasach analiza leksykalna i parsowanie mogą być połączone w jednym kroku.

Oczywiście osoba pisząca generator kodu musi zrozumieć docelowy kod maszynowy na bardzo głębokim poziomie, w tym zestawy instrukcji, potoki procesorów i zachowanie pamięci podręcznej. W przeciwnym razie programy tworzone przez kompilator byłyby powolne i nieefektywne. Bardzo dobrze potrafią czytać i pisać kod maszynowy reprezentowany przez liczby ósemkowe lub szesnastkowe, ale ogólnie piszą funkcje do generowania kodu maszynowego, odnosząc się wewnętrznie do tabel instrukcji maszynowych. Teoretycznie ludzie piszący leksykon i parser mogą nic nie wiedzieć o generowaniu kodu maszynowego. W rzeczywistości niektóre nowoczesne kompilatory umożliwiają podłączenie własnych procedur generowania kodu, które mogą emitować kod maszynowy dla niektórych procesorów, o których nigdy nie słyszeli piszący lexer i parserzy.

Jednak w praktyce autorzy kompilatorów na każdym kroku wiedzą dużo o różnych architekturach procesorów, co pomaga im projektować struktury danych, których będzie potrzebował krok generowania kodu.

Charles E. Grant
źródło
2

Dawno temu napisałem kompilator, który przekonwertował dwa różne skrypty powłoki. Nie zbliżył się do kodu maszynowego.

Zapis kompilatora musi zrozumieć ich dane wyjściowe , ale często nie jest to kod maszynowy.

Większość programistów nigdy nie napisze kompilatora, który generuje kod maszynowy lub kod asemblera, ale kompilatory niestandardowe mogą być bardzo przydatne w wielu projektach do generowania innych danych wyjściowych.

YACC to jeden z takich kompilatorów, który nie generuje kodu maszynowego…

Ian
źródło
0

Nie musisz zaczynać od szczegółowej znajomości semantyki języków wejściowych i wyjściowych, ale lepiej skończyć z wyjątkowo szczegółową znajomością obu języków, w przeciwnym razie kompilator nie będzie miał problemów. Więc jeśli twoje wejście to C ++, a twój wynik to jakiś konkretny język maszynowy, w końcu będziesz musiał znać semantykę obu.

Oto niektóre subtelności w kompilowaniu C ++ do kodu maszynowego: (tuż nad moją głową, jestem pewien, że zapominam o innych).

  1. Jaki będzie rozmiar int? „Właściwy” wybór tutaj jest sztuką opartą zarówno na naturalnym rozmiarze wskaźnika maszyny, wydajności ALU dla różnych wielkości operacji arytmetycznych, jak i wyborach dokonanych przez istniejące kompilatory dla maszyny. Czy maszyna ma nawet 64-bitową arytmetykę? Jeśli nie, to dodanie 32-bitowych liczb całkowitych powinno przełożyć się na instrukcję, podczas gdy dodanie 64-bitowych liczb całkowitych powinno przełożyć się na wywołanie funkcji w celu wykonania dodania 64-bitowego. Czy maszyna ma 8-bitowe i 16-bitowe operacje dodawania, czy też trzeba symulować operacje z 32-bitowymi operacjami i maskowaniem (np. DEC Alpha 21064)?

  2. Jaka jest konwencja wywoływania używana przez inne kompilatory, biblioteki i języki na komputerze? Czy parametry są przesuwane na stos od prawej do lewej, czy od lewej do prawej? Czy niektóre parametry trafiają do rejestrów, a inne stosu? Czy liczby całkowite i zmiennoprzecinkowe znajdują się w różnych przestrzeniach rejestru? Czy parametry przypisane do rejestru muszą być traktowane specjalnie podczas połączeń varargs? Które rejestry są zapisywane do rozmówcy, a które do rozmówcy? Czy możesz przeprowadzić optymalizację połączeń typu „liść”?

  3. Co robi każda z instrukcji zmiany maszyny? Jeśli poprosisz o przesunięcie 64-bitowej liczby całkowitej o 65 bitów, jaki jest wynik? (Na wielu komputerach wynik jest taki sam jak przesunięcie o 1 bit, na innych wynik to „0”.)

  4. Jaka jest semantyka spójności pamięci maszyny? C ++ 11 ma bardzo dobrze zdefiniowaną semantykę pamięci, która nakłada ograniczenia na niektóre optymalizacje w niektórych przypadkach, ale zezwala na optymalizacje w innych przypadkach. Jeśli kompilujesz język, który nie ma dobrze zdefiniowanej semantyki pamięci (jak każda wersja C / C ++ przed C ++ 11 i wiele innych języków imperatywnych), będziesz musiał wymyślić semantykę pamięci w miarę postępów i zwykle będziesz chciał wymyślić semantykę pamięci, która najlepiej pasuje do semantyki maszyny.

Wędrująca logika
źródło