При разработке собственного языка программирования, когда имеет смысл писать конвертер, который берет исходный код и преобразует его в код на языке C или C ++, чтобы я мог использовать существующий компилятор, такой как gcc, для получения машинного кода? Есть проекты, которые используют этот подход?
35
Ответы:
Переход на C-код - очень хорошо сложившаяся привычка. Оригинальный C с классами (и ранние реализации C ++, тогда называемые Cfront ) сделали это успешно. Это делают несколько реализаций Lisp или Scheme, например, Chicken Scheme , Scheme48 , Bigloo . Некоторые люди перевели Пролог на Си . Как и некоторые версии Моцарта (и были попытки скомпилировать байт-код Ocaml в C ). Система CAIA искусственного интеллекта J.Pitrat также загружается и генерирует весь свой C-код. Vala также переводится как C для кода, связанного с GTK. Книга Квиннека Лисп в маленьких кусочках есть глава о переводе на C.
Одна из проблем при переводе на C - это хвостовые рекурсивные вызовы . Стандарт C не гарантирует, что компилятор C переводит их должным образом (в «переход с аргументами», т.е. без использования стека вызовов), даже если в некоторых случаях последние версии GCC (или Clang / LLVM) выполняют эту оптимизацию ,
Другая проблема - сборка мусора . Несколько реализаций просто используют консервативный сборщик мусора Boehm (который дружествен на C ...). Если вы хотите собрать мусор кода (как это делают несколько реализаций Lisp, например, SBCL), это может стать кошмаром (вы хотели бы это сделать
dlclose
в Posix).Еще одна проблема связана с первоклассными продолжениями и call / cc . Но возможны хитрые трюки (загляните внутрь Chicken Scheme). Доступ к стеку вызовов может потребовать много хитростей (но см. Обратный ход GNU и т. Д ....). Ортогональное сохранение продолжений (то есть стеков или потоков) было бы трудно в C.
Обработка исключений часто является вопросом для создания умных вызовов longjmp и т.д ...
Возможно, вы захотите сгенерировать (в вашем испускаемом C-коде) соответствующие
#line
директивы. Это скучно и требует много работы (вы захотите, например, чтобы создать более легкоgdb
отлаживаемый код).Мой специфичный для MELT доменный язык (для настройки или расширения GCC ) переведен на C (фактически на плохой C ++). Он имеет свой собственный сборщик мусора для копирования. (Вас может заинтересовать Qish или Ravenbrook MPS ). На самом деле, генерация GC проще в машинно-сгенерированном C-коде, чем в рукописном C-коде (потому что вы настроите генератор C-кода для своего барьера записи и механизма GC).
Я не знаю какой-либо языковой реализации, переводящей на подлинный код C ++, т. Е. Использующей какую-то технику «сборки мусора во время компиляции» для генерации кода C ++ с использованием большого количества шаблонов STL и уважения идиомы RAII . (скажите, пожалуйста, знаете ли вы один).
Что забавно сегодня, так это то, что (на современных настольных компьютерах Linux) компиляторы C могут быть достаточно быстрыми для реализации интерактивного цикла чтения-оценки- преобразования верхнего уровня, переведенного в C: вы будете испускать код C (несколько сотен строк) для каждого пользователя взаимодействие, вы будете
fork
компилировать его в общий объект, который вы бы тогдаdlopen
. (MELT делает все это готовым, и обычно это достаточно быстро). Все это может занять несколько десятых секунды и быть приемлемым для конечных пользователей.Когда это возможно, я бы рекомендовал переводить на C, а не на C ++, в частности, потому что компиляция C ++ идет медленно.
Если вы реализуете свой язык, вы могли бы также рассмотреть (вместо выпуска кода на C) некоторые библиотеки JIT, такие как libjit , GNU lightning , asmjit или даже LLVM или GCCJIT . Если вы хотите перевести на C, вы можете иногда использовать tinycc : он очень быстро компилирует сгенерированный код C (даже в памяти), чтобы замедлить машинный код. Но в целом вы хотите воспользоваться преимуществами оптимизации, выполняемой настоящим компилятором C, таким как GCC
Если вы переводите на свой язык C, сначала обязательно соберите в памяти весь AST сгенерированного кода C (это также упрощает сначала генерацию всех объявлений, а затем всех определений и кода функции). Вы могли бы сделать некоторые оптимизации / нормализации таким образом. Также вас могут заинтересовать несколько расширений GCC (например, вычисленные gotos). Вы, вероятно, захотите избегать генерации огромных функций C - например, из сотен тысяч строк сгенерированного C - (лучше разбить их на более мелкие части), поскольку оптимизирующие компиляторы C очень недовольны очень большими функциями C (на практике и экспериментально,
gcc -O
время компиляции больших функций пропорционально квадрату размера кода функции). Поэтому ограничьте размер сгенерированных C-функций до нескольких тысяч строк каждая.Обратите внимание, что как Clang (через LLVM ), так и GCC (через libgccjit ) C & C ++ предлагают некоторый способ испускать некоторые внутренние представления, подходящие для этих компиляторов, но это может быть (или нет) сложнее, чем испускание кода C (или C ++), и специфичен для каждого компилятора.
Если вы разрабатываете язык для перевода на C, вы, вероятно, захотите использовать несколько приемов (или конструкций) для создания смеси C с вашим языком. Моя статья DSL2011 MELT: язык для конкретного домена, встроенный в компилятор GCC, должен дать вам полезные советы.
источник
Это имеет смысл, когда время генерации полного машинного кода перевешивает неудобство промежуточного этапа компиляции вашего "IL" в машинный код с использованием компилятора Си.
Обычно доменные языки написаны таким образом, система очень высокого уровня используется для определения или описания процесса, который затем компилируется в исполняемый файл или dll. Время, затрачиваемое на создание работающей / хорошей сборки, намного больше, чем на генерацию C, и C достаточно близок к коду сборки по производительности, поэтому имеет смысл сгенерировать C и повторно использовать навыки авторов компиляторов C. Обратите внимание, что это не просто компиляция, но и оптимизация - парни, которые пишут gcc или llvm, потратили много времени на создание оптимизированного машинного кода, было бы глупо пытаться заново изобрести всю свою тяжелую работу.
Может быть более приемлемо повторно использовать бэкэнд компилятора LLVM, который IIRC не зависит от языка, поэтому вы генерируете инструкции LLVM вместо кода C.
источник
Написание компилятора для создания машинного кода может быть не намного сложнее, чем написание того, который производит C (в некоторых случаях это может быть проще), но компилятор, который создает машинный код, сможет создавать исполняемые программы только на той конкретной платформе, для которой это было написано; напротив, компилятор, который генерирует код на C, может создавать программы для любой платформы, которая использует диалект C, для которого сгенерированный код предназначен. Обратите внимание, что во многих случаях может быть возможно написать код на C, который будет полностью переносимым и который будет вести себя как нужно без использования каких-либо поведений, не гарантированных стандартом C, но код, который опирается на поведения, гарантированные платформой, может быть в состоянии работать намного быстрее на платформах, которые дают те гарантии, чем код, который не делает.
Например, предположим, что язык поддерживает функцию для получения
UInt32
из четырех последовательных байтов произвольно выровненныхUInt8[]
, интерпретируемых в порядке с прямым порядком байтов. На некоторых компиляторах можно написать код как:и пусть компилятор сгенерирует операцию загрузки слова с последующей инструкцией обратного байта в слове. Однако некоторые компиляторы не поддерживают модификатор __packed и при его отсутствии генерируют код, который не будет работать.
В качестве альтернативы можно написать код как:
такой код должен работать на любой платформе, даже на тех, где
CHAR_BITS
нет 8 (при условии, что каждый октет исходных данных заканчивается отдельным элементом массива), но такой код, вероятно, может работать не так быстро, как непереносимый версия на платформах с поддержкой первого.Обратите внимание, что для переносимости часто требуется, чтобы код был чрезвычайно либеральным с типами и аналогичными конструкциями. Например, код, который хочет умножить два 32-разрядных целых числа без знака и получить младшие 32 бита результата, для переносимости должен быть записан как:
Без этого
1u
компилятор в системе, где INT_BITS варьировался от 33 до 64, мог бы на законных основаниях делать все, что хотел, если произведение x и y было больше чем 2 147 483 647, и некоторые компиляторы склонны использовать такие возможности.источник
Вы получили несколько превосходных ответов выше, но, учитывая, что в комментарии вы ответили на вопрос «Почему вы хотите создать свой собственный язык программирования в первую очередь?» На «Это было бы в основном для целей обучения», «Я» Я собираюсь ответить под другим углом.
Имеет смысл написать конвертер, который берет исходный код и преобразует его в код на языке C или C ++, так что вы можете использовать существующий компилятор, такой как gcc, для получения машинного кода, если вы больше заинтересованы в изучении лексического, синтаксического и семантический анализ, чем вы в изучении генерации кода и оптимизации!
Написание собственного генератора машинного кода - довольно значительная часть работы, которую вы можете избежать, компилируя в C-код, если это не то, что вас в первую очередь интересует!
Однако, если вы участвуете в программе сборки и увлечены проблемами оптимизации кода на самом низком уровне, то непременно создайте генератор кода самостоятельно для обучения!
источник
Это зависит от того, какую операционную систему вы используете, если вы используете Windows, существует Microsoft IL (промежуточный язык), который преобразует ваш код в промежуточный язык, так что вам не нужно время для компиляции в машинный код. Или, если вы используете Linux, для этого есть отдельный компилятор
Возвращаясь к вашему вопросу, когда вы разрабатываете свой собственный язык, у вас должен быть отдельный компилятор или интерпретатор для этого, потому что машина не знает язык высокого уровня. Ваш код должен быть скомпилирован в машинный код, чтобы сделать его полезным для машины
источник
Your code should be compiled into machine code to make it useful for machine
- Если ваш компилятор выдает код c в качестве вывода, вы можете поместить код c в компилятор ac, чтобы получить машинный код, верно?