Tłumaczenie kodu C jest bardzo dobrze ugruntowanym nawykiem. Oryginalne C z klasami (i wczesne implementacje C ++, zwane wtedy Cfront ) zrobiły to z powodzeniem. Robi to kilka implementacji Lisp lub Scheme, np. Chicken Scheme , Scheme48 , Bigloo . Niektórzy ludzie tłumaczone Prolog do C . Podobnie stało się z niektórymi wersjami Mozarta (i próbowano skompilować kod bajtowy Ocaml do C ). System CAIA sztucznej inteligencji J.Pitrat jest również ładowany i generuje cały swój kod C. Vala tłumaczy również na C dla kodu związanego z GTK. Książka Queinnec Lisp In Small Pieces mieć rozdział o tłumaczeniu na C.
Jednym z problemów przy tłumaczeniu na C są wywołania rekurencyjne . Standard C nie gwarantuje, że kompilator C przetłumaczy je poprawnie (na „skok z argumentami”, tj. Bez spożywania stosu wywołań), nawet jeśli w niektórych przypadkach najnowsze wersje GCC (lub Clang / LLVM) dokonują takiej optymalizacji .
Kolejnym problemem jest zbieranie śmieci . Kilka implementacji korzysta tylko z konserwatywnego śmieciarza Boehm (który jest przyjazny dla C ...). Jeśli chcesz wyrzucić śmieci (tak jak robi to kilka implementacji Lisp, np. SBCL), może to być koszmar (chciałbyś dlclose
na Posix).
Jeszcze inna sprawa dotyczy pierwszorzędnych kontynuacji i call / cc . Ale możliwe są sprytne sztuczki (zajrzyj do Kurczaka). Dostęp do stosu wywołań może wymagać wielu trików (ale patrz śledzenie GNU itp.). Ortogonalna trwałość kontynuacji (tj. Stosów lub nici) byłaby trudna w C.
Obsługa wyjątków jest często kwestią emitowania sprytnych połączeń do longjmp itp.
Możesz wygenerować (w emitowanym kodzie C) odpowiednie #line
dyrektywy. Jest to nudne i zajmuje dużo pracy (będziesz chciał, aby np. gdb
Stworzyć łatwiejszy do debugowania kod).
Mój język specyficzny dla domeny MELT lispy (w celu dostosowania lub rozszerzenia GCC ) jest przetłumaczony na język C (obecnie na słaby C ++). Ma swój własny generator kopiujący śmieci. (Być może zainteresuje Cię Qish lub Ravenbrook MPS ). W rzeczywistości generowanie GC jest łatwiejsze w generowanym maszynowo kodzie C niż w ręcznie napisanym kodzie C (ponieważ dostosujesz generator kodu C do bariery zapisu i maszyn GC).
Nie znam żadnej implementacji języka tłumaczącej na oryginalny kod C ++, tj. Używającej techniki „gromadzenia pamięci podczas kompilacji” do emitowania kodu C ++ przy użyciu wielu szablonów STL i szanujących idiom RAII . (proszę powiedzieć, jeśli znasz).
Dziwne jest dziś to, że (na obecnych komputerach z systemem Linux) kompilatory C mogą być wystarczająco szybkie, aby zaimplementować interaktywną pętlę read-eval-print- top przetłumaczoną na język C: będziesz emitować kod C (kilkaset linii) dla każdego użytkownika interakcji, będziesz fork
kompilować go w obiekt współdzielony, który wtedy będziesz dlopen
. (MELT robi to wszystko gotowe i zwykle jest wystarczająco szybkie). Wszystko to może zająć kilka dziesiątych sekundy i może być zaakceptowane przez użytkowników końcowych.
Jeśli to możliwe, polecam tłumaczenie na C, a nie na C ++, w szczególności dlatego, że kompilacja w C ++ jest powolna.
Jeśli implementujesz swój język, możesz również rozważyć (zamiast emitować kod C) niektóre biblioteki JIT, takie jak libjit , błyskawica GNU , asmjit , a nawet LLVM lub GCCJIT . Jeśli chcesz przetłumaczyć na C, możesz czasami użyć tinycc : kompiluje bardzo szybko wygenerowany kod C (nawet w pamięci), aby spowolnić kod maszynowy. Ale ogólnie chcesz skorzystać z optymalizacji przeprowadzonych przez prawdziwy kompilator C, taki jak GCC
Jeśli tłumaczysz na swój język C, pamiętaj, aby najpierw skompilować cały AST wygenerowanego kodu C w pamięci (ułatwia to również wygenerowanie najpierw wszystkich deklaracji, a następnie wszystkich definicji i kodu funkcji). W ten sposób można dokonać optymalizacji / normalizacji. Ponadto możesz być zainteresowany kilkoma rozszerzeniami GCC (np. Gotos komputerowy). Prawdopodobnie będziesz chciał uniknąć generowania ogromnych funkcji C - np. Ze stu tysięcy linii wygenerowanego C - (lepiej podzielisz je na mniejsze części), ponieważ optymalizacja kompilatorów C jest bardzo niezadowolona z bardzo dużych funkcji C (w praktyce i doświadczalnie,gcc -O
czas kompilacji dużych funkcji jest proporcjonalny do kwadratu wielkości kodu funkcji). Więc ogranicz rozmiar generowanych funkcji C do kilku tysięcy linii każda.
Zauważ, że zarówno kompilatory Clang (przez LLVM ), jak i GCC (przez libgccjit ) C & C ++ oferują jakiś sposób na emisję wewnętrznych reprezentacji odpowiednich dla tych kompilatorów, ale może to (lub nie) być trudniejsze niż emisja kodu C (lub C ++), i jest specyficzny dla każdego kompilatora.
Jeśli projektujesz język, który ma zostać przetłumaczony na C, prawdopodobnie potrzebujesz kilku sztuczek (lub konstrukcji), aby wygenerować mieszankę C z twoim językiem. Mój dokument DSL2011 MELT: przetłumaczony język specyficzny dla domeny osadzony w kompilatorze GCC powinien dać ci przydatne wskazówki.