Общие правила написания компилятора X для Z в Y

9

Предположим, X является языком ввода, Z является языком вывода, затем f является компилятором, который написан на языке Y.

f = X -> Z

Поскольку f - это всего лишь программа, я думаю, что Y может быть любым языком, верно? Таким образом, мы можем иметь компиляторы f1, f2, каждый из которых написан на Y1, Y2.

f1 = f Y1    
f2 = f Y2

g = Z -> M
h = g . f    # We get a compiler X -> M

Возьмите, например, компилятор cpython, X - это Python, Z - это код Python VM, Y - это C.

cpython = Python -> PythonVMCode C
interpreter = PythonVMCode -> Nothing
interpreter2 = PythonVMCode -> MachineCode

Исходные коды Python компилируются в код Python VM, файлы .pyc, а затем интерпретируются интерпретатором. Похоже, что существует вероятность того, что существует компилятор, который может напрямую выполнять Python -> MachineCode, хотя его сложно реализовать:

   hardpython = interpreter2 . cpython 

Мы также можем написать другой компилятор, выполняющий работу Python -> PythonVMCode, на другом языке, скажем, сам Python.

mypython = Python -> PythonVMCode Python
mypython2 = Python -> PythonVMCode Ruby

Теперь вот сложный пример PyPy. Я просто новичок в PyPy, поправьте меня, если я ошибаюсь:

Документ PyPy http://doc.pypy.org/en/latest/architecture.html#pypy-the-translation-framework

Наша цель состоит в том, чтобы обеспечить возможное решение проблемы разработчиков языка: необходимость писать l * o * p интерпретаторов для l динамических языков и p платформ с критически важными проектными решениями.

Мы можем думать, что l - это X, p - это Y. Существует программа, которая переводит все программы RPython в C:

 rpython_compiler = RPython -> C  Python

 pypy = Python -> Nothing RPython

 translate = compile the program pypy written in RPython using rpython_compiler

 py2rpy = Python -> RPython  Python
 py2c = Python -> C Python 
 py2c = rpython_compiler . py2rpy

Программы RPython аналогичны инструкциям виртуальной машины, а rpython_compiler - виртуальная машина.

q1. pypy - интерпретатор, программа RPython, которая может интерпретировать код Python, языка вывода нет, поэтому мы не можем рассматривать его как компилятор, верно?

Добавлено:

  • Я только что обнаружил, что даже если после перевода pypy все еще является переводчиком, только на этот раз написанным на C.
  • Если мы углубимся в интерпретатор pypy, я считаю, что должен существовать какой-то компилятор, который компилирует исходники Python в некоторый AST, а затем выполняет

нравится:

compiler_inside_pypy = Python -> AST_or_so

q2. Может ли существовать компилятор py2rpy, преобразующий все программы Python в RPython? На каком языке это написано, не имеет значения. Если да, мы получаем другой компилятор py2c. В чем разница между pypy и py2rpy в природе? Гораздо сложнее написать py2rpy, чем pypy?

q3. Есть ли какие-то общие правила или теории об этом?

Больше компиляторов:

gcc_c = C -> asm? C  # not sure, gimple or rtl?
g++ =   C++ -> asm? C
clang = C -> LLVM_IR  C++
jython = Python -> JVMCode java
ironpython = Python -> CLI C#

q4. Учитывая f = X -> Z, программа P написана на X. Когда мы хотим ускорить P, что мы можем сделать? Возможные способы:

  • переписать P в более эффективный алгоритм

  • переписать F, чтобы генерировать лучше Z

  • если интерпретируется Z, напишите лучший интерпретатор Z (PyPy здесь?)

  • ускорить программы, написанные на Z рекурсивно

  • получить лучшую машину

пс. Этот вопрос не о технических вещах о том, как написать компилятор, а о целесообразности и сложности написания компилятора определенного вида.

jaimechen
источник
Не имеет прямого отношения, но несколько похожая концепция: en.wikipedia.org/wiki/Supercompilation
SK-logic
1
Я не уверен, что этот вопрос действительно подходит для переполнения стека, тем более что в нем так много подвопросов, но я все еще восхищаюсь мыслью, которая вошла в это.
4
Несмотря на то, что вы, возможно, учили, AST не требуется - это просто стратегия, которую используют некоторые компиляторы.
1
Вероятно, это принадлежит cstheory.stackexchange.com
9000
3
Реализация Python PyPy, как и большинство «интерпретаторов», на самом деле является компилятором байт-кода и интерпретатором для этого формата байт-кода в одном.

Ответы:

4

q1. pypy - интерпретатор, программа RPython, которая может интерпретировать код Python, языка вывода нет, поэтому мы не можем рассматривать его как компилятор, верно?

PyPy похож на CPython, у обоих есть компилятор + интерпретатор. CPython имеет компилятор, написанный на C, который компилирует байт-код Python в Python VM, затем выполняет байт-код в интерпретаторе, написанном на C. PyPy имеет компилятор, написанный на RPython, который компилирует байт-код Python в Python VM, а затем выполняет его в интерпретаторе PyPy, написанном на RPython.

q2. Может ли существовать компилятор py2rpy, преобразующий все программы Python в RPython? На каком языке это написано, не имеет значения. Если да, мы получаем другой компилятор py2c. В чем разница между pypy и py2rpy в природе? Гораздо сложнее написать py2rpy, чем pypy?

Может ли существовать компилятор py2rpy? Теоретически да. Такова полнота Тьюринга .

Один из способов построения py2rpy- это просто включить исходный код интерпретатора Python, написанного на RPython, в сгенерированный исходный код. Пример компилятора py2rpy, написанный на Bash:

// suppose that /pypy/source/ contains the source code for pypy (i.e. Python -> Nothing RPython)
cp /pypy/source/ /tmp/py2rpy/pypy/

// suppose $inputfile contains an arbitrary Python source code
cp $inputfile /tmp/py2rpy/prog.py

// generate the main.rpy
echo "import pypy; pypy.execfile('prog.py')" > /tmp/py2rpy/main.rpy

cp /tmp/py2rpy/ $outputdir

теперь, когда вам нужно перевести код Python в код RPython, вы вызываете этот сценарий, который создает в $ outputdir RPython main.rpy, исходный код интерпретатора Python RPython и двоичный двоичный объект prog.py. И тогда вы можете выполнить сгенерированный скрипт RPython, вызвав rpython main.rpy.

(примечание: поскольку я не знаком с проектом rpython, синтаксис для вызова интерпретатора rpython, возможность импортировать pypy и делать pypy.execfile и расширение .rpy чисто составлены, но я думаю, вы поняли)

q3. Есть ли какие-то общие правила или теории об этом?

Да, любой язык Turing Complete теоретически может быть переведен на любой язык Turing Complete. Некоторые языки могут быть намного сложнее перевести, чем другие языки, но если вопрос «возможно ли это?», Ответ «да»

q4. ...

Здесь нет вопросов.

Ли Райан
источник
Ваш компилятор py2rpy действительно умный. Это приводит меня к другой идее. 1. Pypy должен быть написан на RPython в вашем компиляторе? Все, что вам нужно, это что-то, что может интерпретировать файлы Python, верно? 2. os.system ('python $ inputfile') также может работать, если он поддерживается в RPython. Не уверен, можно ли его еще назвать компилятором, по крайней мере, не в буквальном смысле.
Pypy все еще использует Python VM? Теперь понятно. pypy_the_compiler = Python -> PythonVMCode RPython, pypy_the_interpreter = PythonVMCode -> Nothing RPython, cpython_the_compiler = Python -> PythonVMCode C, cpython_the_interpreter = PythonVMCode -> Nothing C
@jaimechen: Does pypy have to be written in RPython in your compiler?Нет, его не нужно писать на RPython, но RPython должен быть в состоянии указать «вспомогательному интерпретатору» / «среде выполнения» выполнить код Python. Да, это правда, это не «компилятор» в практическом смысле, но это конструктивное доказательство того, что можно написать Python -> RPython. Is pypy still using the Python VM?Я считаю, что Pypy вообще не использует CPython (я могу ошибаться), вместо этого PyPy имеет свою собственную реализацию "Python VM", написанную на RPython.
Ли Райан
@jaimechen: более практичный компилятор мог бы проанализировать входной файл на наличие последовательностей кода, которые он знает, как скомпилировать и скомпилировать их по отдельности, а также способ перехода между Python «пересобранный в RPython» и «интерпретатор- Пособие "Питон. Он также может использовать методы, обычно используемые в JIT-компиляции, для определения того, может ли конкретный ввод давать разные выходные данные из-за различий в семантике RPython и Python и отступления к интерпретации в этих случаях. Все это изощренность, которую можно увидеть в более практичном Python -> RPythonкомпиляторе.
Ли Райан
Может быть, здесь следует добавить ограничение: преобразовать конечный автомат X в конечный автомат Z без помощи существующего третьего автомата. Это тот случай, когда X совершенно новый, компилятор или интерпретатор до сих пор не существует.
Jaimechen
2

Чтобы ответить только на вопрос q2, есть книга компиляторов Уильяма МакКимана, в которой теория компиляторов для языка X, написанных на языке Y, производящих язык вывода Z, исследуется с помощью системы T-диаграмм. Опубликовано в 1970-х, название не передать, извините.

user207421
источник
Да, это все, спасибо. en.wikipedia.org/wiki/Tombstone_diagram
jaimechen
1

q1. Как правило, интерпретатор не является компилятором. Основное различие между компилятором и интерпретатором состоит в том, что интерпретатор запускается заново, каждый раз с исходным кодом на исходном языке. Если вместо pypy был pyAST или pyP-код, а затем у вас был интерпретатор AST или P-кода, то вы могли бы назвать pyAST компилятором. Вот как работал старый компилятор UCSD PASCAL (как и многие другие): они компилировались в некоторый P-код, который интерпретировался при запуске программы. (Даже .NET предоставляет нечто подобное, когда компактность сгенерированного объектного кода гораздо важнее скорости.)

q2. Ну конечно; естественно. Смотрите UCSD PASCAL (и кучу других).

q3. Копайте классические тексты по информатике. Прочтите «Параллельный ПАСКАЛЬ» Пера Бринч-Хансена (если мне не изменяет память). Много написано о компиляторах и генерации кода. Генерировать машинно-независимый псевдокод обычно намного проще, чем генерировать машинный код: псевдокод обычно свободен от причуд, которые неизменно содержатся в реальных машинах.

q4. Если вы хотите, чтобы сгенерированный объект работал быстрее, вы сделаете компилятор умнее, чтобы оптимизировать работу. Если ваш объект интерпретируется, вы рассматриваете возможность внедрения более сложных операций в примитивные псевдоинструкции (аналогия между CISC и RISC), тогда вы сделаете все возможное, чтобы оптимизировать фрак из вашего интерпретатора.

Если вы хотите, чтобы ваш компилятор работал быстрее, вам нужно посмотреть на ВСЕ, что он делает, включая переосмысление исходного кода. После загрузки самого компилятора наиболее трудоемкой частью компиляции является ВСЕГДА чтение исходного кода в компилятор. (Рассмотрим, например, C ++. При прочих равных условиях компилятор, который должен сжать 9 000 (или, может быть, 50 000) строк файлов #include для компиляции простой программы «Hello, World», никогда не будет настолько быстрым, как один. нужно только прочитать четыре или пять строк.)

Я не помню, где я это читал, но оригинальный компилятор Oberon в ETH-Zurich имел очень сложный механизм таблицы символов, довольно элегантный. Тест Вирта на производительность компилятора - это время компиляции самого компилятора. Однажды утром он вошел, вытащил великолепную многосвязную таблицу символов ультра-дерева и заменил ее простым линейным массивом и прямыми линейными поисками. Аспиранты в его группе были шокированы. После изменения компилятор был быстрее, потому что модули, которые он компилировал, всегда были достаточно малы, чтобы элегантный монстр требовал больше общих затрат, чем линейный массив и линейный поиск.

Джон Р. Штром
источник
1
Спасибо. Компилятор «компилирует», а интерпретатор «выполняет», может ли быть больше понимания двух типов программ, так как их типы различны?
Jaimechen
1

Ваши вопросы в том виде, в котором они изложены, заставляют меня поверить, что вы действительно хотите / нуждаетесь в объяснении того, что такое компилятор, что такое интерпретатор и различия между ними.

Компилятор отображает программу, написанную на языке X, в функционально эквивалентную программу, написанную на языке Y. Например, компилятор из Паскаля в C может скомпилировать

function Square(i: Integer)
begin
    Square := i * i
end

в

int Square(int i)
{
    return i * i;
}

Большинство компиляторов компилируют «вниз», поэтому они компилируют языки программирования более высокого уровня в языки более низкого уровня, причем конечным языком нижнего уровня является машинный код.

Большинство компиляторов компилируются непосредственно в машинный код, но некоторые (особенно языки Java и .NET) компилируются в «байт-код» ( байт-код Java и CIL ). Думайте о байт-коде как о машинном коде для гипотетического компьютера. Этот байт-код затем интерпретируется или JITted при запуске (подробнее об этом позже).

Интерпретатор выполняет программу, написанную на некотором языке Z. Интерпретатор читает программу по крупицам, выполняя ее по ходу дела. Например:

int i = 0;
while (i < 1)
{
    i++
}
return i;

Представьте, что переводчик смотрит на эту строку программы за строкой, изучает строку, выполняет то, что он делает, смотрит на следующую строку и так далее.

Лучший пример переводчика - процессор вашего компьютера. Он интерпретирует машинный код и выполняет его. Как работает процессор, определяется тем, как он построен физически. Как работает программа-интерпретатор, определяется тем, как выглядит ее код. Таким образом, CPU интерпретирует и выполняет программу интерпретатора, которая, в свою очередь, интерпретирует и выполняет свой ввод. Вы можете связать переводчиков таким образом.

JITter - это компилятор Just-In-Time. JITter - это компилятор. Единственная разница заключается во времени выполнения: большинство программ пишутся, компилируются, отправляются их пользователям, а затем выполняются, но байт-код Java и CIL сначала отправляются своим пользователям, а затем непосредственно перед выполнением они компилируются на компьютер. код их пользователей.

C # -> (компилировать) -> CIL -> отправлено клиенту -> (компилировать непосредственно перед выполнением) -> машинный код -> (выполнить)

Последнее, что вы хотите знать, это полнота по Тьюрингу ( ссылка ). Язык программирования является полным по Тьюрингу, если он может вычислять все, что может « машина Тьюринга », т. Е. Он по меньшей мере такой же «мощный», как и машина Тьюринга. В тезис Черча-Тьюринга утверждает , что машина Тьюринга является менее мощной , как и любой машине , которую мы когда - либо можно строить. Из этого следует, что каждый полный язык Тьюринга так же мощен, как и машина Тьюринга, и, следовательно, все языки полного Тьюринга одинаково мощны.

Другими словами, до тех пор, пока ваш язык программирования завершен по Тьюрингу (почти все они), не имеет значения, какой язык вы выберете, поскольку все они могут вычислять одни и те же вещи. Это также означает, что не очень важно, какой язык программирования вы выберете для написания компилятора или интерпретатора. И последнее, но не менее важное: это означает, что вы всегда можете написать компилятор с языка X на Y, если X и Y оба завершены по Тьюрингу.

Обратите внимание, что полное завершение по Тьюрингу не говорит ни о том, эффективен ли ваш язык, ни обо всех деталях реализации вашего ЦП и другого оборудования, ни о качестве компилятора, который вы используете для языка. Кроме того, ваша операционная система может решить, что ваша программа не имеет прав на открытие файла, но это не ограничивает вашу способность что-либо вычислять - я сознательно не определял вычисления, поскольку для этого потребовалась бы другая стена текста.

Алекс тен Бринк
источник