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.
compiler
machine-code
Aviv Cohn
źródło
źródło
Odpowiedzi:
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.
źródło
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.
źródło
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.
źródło
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…
źródło
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).
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)?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ść”?
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”.)
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.
źródło