О способах компиляции Питон (сочинение, объясняющее, почему автор писал транслятор так, а не иначе)
Вообще, к компилятору интерпретируемого языка всегда есть два главных требования:
a) Увеличение скорости выполнения программ
b) 100% совместимость
Строго говоря, увеличить скорость - не проблема. Книжку Ахо и Ульмана в зубы - и вперед! В конце концов, мы (программисты) пишем трансляторы 50 лет, основные трюки оптимизирующей трансляции давно изучены.
Проблема, а вернее - громадное, как облако сибирского гнуса, скопление мелких проблемок - совместимость. Не очень нужен очень эффективный транслятор немножко не того языка, к которому все привыкли. Например, сейчас существует по крайней мере три транслятора Питоно-подобного языка в С (С++). Все они очень сильно несовместимы с Питоном
Из них самый быстрый код генерит Shed-skin, а более-менее используются другие два (Pyrex - для написания модулей расширения и склейки с С кодом, Cython (сам разработанный на базе Pyrex) - в какой-то большой программе символьной и не только математики).
Покажу, о какой несовместимости идет речь таким куском Питон кода, который выполняется интерпретатором, но не транслируется ни одним из трех трансляторов:
i f system == 'Windows':
def is_alarm():
return True
def is_alarm():
return False
Т.е функция is_alarm создается динамически с разным кодом. Или, в терминах интерпретатора Питона - переменной is_alarm присваиваются разные кодовые объекты.
Я не сомневаюсь, что данный конкретный код может быть оттранслирован любым из них после 'доработки напильником'. Но таких конструкций, использующих динамизм языка, очень много. Напильник сотрешь. И выходит, что
Странслированная в С Питон-программа должна полностью поддерживать среду Питон (в общем случае)
Мне лень самому выписывать внутренности Питона. Да и зачем ? Берем run-time интерпретатора, подкладываем его под сгенеренный С-код - и что ? Странслированная программа обращается к другому Питон модулю, который никто и не думает компилировать в С. Так что и интерпретатор целиком, то есть весь CPython считаю своим ран-таймом. После чего лечу манию величия, и понимаю, что я написал/пишу просто еще один модуль Питона, который реализует ф-ю трансляции в двоичный исполнимый код (через С).
Примечание первое. Почему транслирую в С.
Вообще, при прямых руках и ловких пальчиках программа, странслированная напрямую в двоичный код, будет несколько быстрее и гарантированно меньше, чем аналогичная программа, странслированная в двоичный код через промежуточное представление в виде С-кода. Это не обсуждается. Возникают только два вопроса. Первое. Компьютеров много, и не все из них PC . Второе. Если все подпрограммы написаны на C , стоит ли извращаться, вызывая их из маш кода?
История Psyco - вам например. Отличное решение, близкая к совершенству работа. Вот там - да, там генерится двоичный код напрямую. Но. Проблему с кроссплатформенностью Psyco поимел. Вернее. (Гусары, молчать!). И двоичный код там естественен, так как в Psyco на ассемблере переписана часть библиотеки поддержки и интерпретатора. Но сейчас автор Psyco не поддерживает его для новых версий CPython , а занимается JT компилятором в PyPy . Уже несколько лет занимается.
То бишь о чем я. Да, о проблемах трансляции Питон-программ.
Другой момент, когда возникает несовместимость -- когда интерпретатор и компилятор используют разные синтаксические и/или семантические анализаторы. Кроме того, что это избыточно и коряво, это еще и гарантированный способ получить несовместимость. (И именно исходя из этого, на переходе с 2.6 на 3.0, команда CPython отцепила хвостовой локомотив от поезда - модуль compiler исключен из дистрибутива.)
А между тем тот же Shed-skin этот модуль использует.
Есть интерфейс на уровне AST-дерева. Можно было бы использовать его. Но я предпочел прийти на готовенькое и в качестве входных данных для трансляции использовать питоновский байт-код. Да, это "немножко" неудобно. Но по крайней мере всегда можно открыть соответствующую строчку в интерпретаторе и посмотреть точную семантику. Причем в терминах С.
Правда, прежде чем странслировать в С, байт-код пришлось немного рекомпилировать до уровня выражений и структурных операторов (результат рекомпиляции можно посмотреть в файлах с расширением .pycmd. Это своего рода промежуточный результат трансляции.) А полученный псевдокод уже транслируем в С. С СОХРАНЕНИЕМ ДИНАМИЧЕСКОЙ СРЕДЫ ПИТОНА .
А сохранение среды означает использование в качестве основной структуры данных PyFrameObject объекта Питона. Вообще в интерпретаторе ее получает в качестве единственного параметра ф-я PyEval_EvalFrame. Она содержит в себе ссылки на локальные и глобальные переменные, на выполняемый байт-код, на константы. Короче говоря, она полностью определяет контекст вычислений. И интерпретатор берет ее, раскрывает ее в локальные С-переменные, и обрабатывает код в ней.
Примерно так же работают и сгенеренные на основании байт-кода С-функции. (С некоторым отличием - вместо динамического моделирования стека значений при вычислении выражений исползуется статическое отображение стека на временные С-переменные типа PyObject *.) И тут мы получаем бонус -- раз структура данных стандартная, то передача/прием параметров происходят почти автоматом. То есть передача параметров между вызовами происходит пусть не очень быстро, но невидимо. Если кому интересно, попробуйте в Cython странслировать десятистрочную ф-ю с пятью параметрами. Увидите, что прием аргументов занимает больше места, чем собственно код ф-и.
И тут наступаем на грабли сохранения совместимости. Если сгенеренный код - работает с теми же структурами данных что и интерпретатор, то вопрос - а когда надо вызывать компилированную функцию, а когда данные отправлять в интерпретатор? Ну, нет в стандартной структуре такого поля!
Иными словами, если мы скомпилировали код
import test1
То, не зная test1 мы не можем даже сказать, вызывается test3 или нет. Это можно определить только динамически.
Поэтому добавляю в структуру PyCodeObject поле - ссылка на С-функцию, вычисляющую этот PyCodeObject. И при создании этого объекта ссылку заполняю. А при всяком вызове PyEval_EvalCodeEx проверяем. Все. Можно присваивать элементу списка, сортировать - все равно при вызове PyEval_EvalCodeEx посмотрим на это поле и выполним или интерпретатор или функцию из этой ссылки.
И именно это расширенное на одно поле PyCodeObject не позволит запустить с неподправленным интерпретатором Питона скомпилированный код. Ошибка обращения к памяти, понимаешь.
Ну, на сегодня все
P.S хотел я это все поместить в комменты к предыдущей теме, да не поместилось по размеру</lj-cut>