- •Об авторе
- •О группе редакторов
- •Предисловие
- •Введение
- •Как использовать эту книгу
- •Загрузка исходного кода CPython
- •Что в исходном коде?
- •Настройка среды разработки
- •IDE или редактор?
- •Настройка Visual Studio
- •Настройка Visual Studio Code
- •Настройка Vim
- •Выводы
- •Компиляция CPython
- •Компиляция CPython на macOS
- •Компиляция CPython на Linux
- •Установка специализированной версии
- •Знакомство с Make
- •Make-цели CPython
- •Компиляция CPython на Windows
- •Профильная оптимизация
- •Выводы
- •Грамматика и язык Python
- •Спецификация языка Python
- •Генератор парсеров
- •Повторное генерирование грамматики
- •Выводы
- •Конфигурация и ввод
- •Конфигурация состояния
- •Структура данных конфигурации среды выполнения
- •Конфигурация сборки
- •Сборка модуля из входных данных
- •Выводы
- •Генерирование конкретного синтаксического дерева
- •Парсер/токенизатор CPython
- •Абстрактные синтаксические деревья
- •Важные термины
- •Пример: добавление оператора «почти равно»
- •Выводы
- •Компилятор
- •Исходные файлы
- •Важные термины
- •Создание экземпляра компилятора
- •Флаги будущей функциональности и флаги компилятора
- •Таблицы символических имен
- •Основная компиляция
- •Ассемблер
- •Создание объекта кода
- •Использование Instaviz для вывода объекта кода
- •Пример: реализация оператора «почти равно»
- •Выводы
- •Цикл вычисления
- •Исходные файлы
- •Важные термины
- •Построение состояния потока
- •Построение объектов кадров
- •Выполнение кадра
- •Стек значений
- •Пример: добавление элемента в список
- •Выводы
- •Управление памятью
- •Выделение памяти в C
- •Проектирование системы управления памятью Python
- •Аллокаторы памяти CPython
- •Область выделения объектной памяти и PyMem
- •Область выделения сырой памяти
- •Нестандартные области выделения памяти
- •Санитайзеры выделенной памяти
- •Арена памяти PyArena
- •Подсчет ссылок
- •Сборка мусора
- •Выводы
- •Параллелизм и конкурентность
- •Модели параллелизма и конкурентности
- •Структура процесса
- •Многопроцессорный параллелизм
- •Многопоточность
- •Асинхронное программирование
- •Генераторы
- •Сопрограммы
- •Асинхронные генераторы
- •Субинтерпретаторы
- •Выводы
- •Объекты и типы
- •Примеры этой главы
- •Встроенные типы
- •Типы объектов
- •Тип type
- •Типы bool и long
- •Тип строки Юникода
- •Словари
- •Выводы
- •Стандартная библиотека
- •Модули Python
- •Модули Python и C
- •Набор тестов
- •Запуск набора тестов в Windows
- •Запуск набора тестов в Linux или macOS
- •Флаги тестирования
- •Запуск конкретных тестов
- •Модули тестирования
- •Вспомогательные средства тестирования
- •Выводы
- •Отладка
- •Обработчик сбоев
- •Компиляция поддержки отладки
- •LLDB для macOS
- •Отладчик Visual Studio
- •Отладчик CLion
- •Выводы
- •Бенчмаркинг, профилирование и трассировка
- •Использование timeit для микробенчмарка
- •Использование набора тестов производительности Python
- •Профилирование кода Python с использованием cProfile
- •Выводы
- •Что дальше?
- •Создание расширений C для CPython
- •Улучшение приложений Python
- •Участие в проекте CPython
- •Дальнейшее обучение
- •Препроцессор C
- •Базовый синтаксис C
- •Выводы
- •Благодарности
Основная компиляция 121
VISIT_QUIT(st, 0);
if (s->v.FunctionDef.args->defaults)
VISIT_SEQ(st, expr, s->v.FunctionDef.args->defaults); if (s->v.FunctionDef.args->kw_defaults)
VISIT_SEQ_WITH_NULL(st, expr, s->v.FunctionDef.args->kw_defaults);
if (!symtable_visit_annotations(st, s, s->v.FunctionDef.args, s->v.FunctionDef.returns))
VISIT_QUIT(st, 0);
if (s->v.FunctionDef.decorator_list)
VISIT_SEQ(st, expr, s->v.FunctionDef.decorator_list); if (!symtable_enter_block(st, s->v.FunctionDef.name,
FunctionBlock, (void *)s, s->lineno, s->col_offset))
VISIT_QUIT(st, 0);
VISIT(st, arguments, s->v.FunctionDef.args); VISIT_SEQ(st, stmt, s->v.FunctionDef.body); if (!symtable_exit_block(st, s))
VISIT_QUIT(st, 0); break;
case ClassDef_kind: {
...
}
case Return_kind:
...
case Delete_kind:
...
case Assign_kind:
...
case AnnAssign_kind:
...
После того как таблица символических имен будет создана, она передается компилятору.
ОСНОВНАЯ КОМПИЛЯЦИЯ
Итак, теперь, когда у PyAST_CompileObject() имеется состояние компилятора, таблица символических имен и модуль в форме AST, можно начинать фактическую компиляцию.
Основной компилятор решает две задачи:
1.Преобразование состояния, таблицы символических имен и AST в граф потока управления (CFG).
Книги для программистов: https://t.me/booksforits
122 Компилятор
2.Предупреждение возникновения исключений на стадии исполнения посредством перехвата логических ошибок и ошибок кода.
Обращение к компилятору из Python
Компилятор можно вызвать из кода Python при помощи встроенной функции compile(). Функция возвращает объект кода (code object):
>>>co = compile("b+1", "test.py", mode="eval")
>>>co
<code object <module> at 0x10f222780, file "test.py", line 1>
Как и в случае с symtable() API, простое выражение должно использовать режим (mode) "eval", а модуль, функция или класс — режим "exec".
Cкомпилированный код находится в свойстве co_code объекта кода:
>>> co.co_code b'e\x00d\x00\x17\x00S\x00'
Стандартная библиотека также включает модуль dis, дизассемблирующий инструкции байт-кода. Их можно вывести на экран или получить список экземпляров Instruction.
ПРИМЕЧАНИЕ
Тип Instruction в модуле dis является отражением типа instr из C API.
Если импортировать модуль dis и передать dis() свойство co_code объекта кода, то функция дизассемблирует его и выведет инструкции в REPL:
>>>import dis
>>>dis.dis(co.co_code)
0 LOAD_NAME 0 (0)
2 LOAD_CONST 0 (0)
4 BINARY_ADD
6 RETURN_VALUE
LOAD_NAME, LOAD_CONST, BINARY_ADD и RETURN_VALUE — инструкции байткода. Термин «байт-код» объясняется тем, что в двоичной форме длина
Книги для программистов: https://t.me/booksforits
Основная компиляция 123
инструкций составляет 1 байт. Впрочем, начиная с Python 3.6, формат хранения был расширен до машинного слова1, так что формально это «слово-код», а не «байт-код».
Полый список инструкций байт-кода2 доступен для каждой версии Python, причем он изменяется между версиями. Например, в Python 3.7 появились новые инструкции байт-кода, ускоряющие вызов некоторых методов.
В предшествующих главах мы рассмотрели пакет instaviz. Его функциональность включает визуализацию типа объекта кода посредством запуска компилятора. Кроме того, instaviz выводит операции байт-кода внутри объектов кода.
Снова выполните instaviz, чтобы просмотреть объект кода и байт-код для функции, определенной в REPL:
>>>import instaviz
>>>def example(): a = 1
b = a + 1 return b
>>>instaviz.show(example)
API компилятора на C
Точка входа для компиляции модулей AST, compiler_mod(), переключается на разные функции компилятора в зависимости от типа модуля. Если в mod установлен тип Module, модуль компилируется в качестве единиц компиляции в свойство c_stack. Затем запускается функция assemble() для создания объекта PyCodeObject из стека единиц компиляции.
Возвращается новый объект кода, который передается на выполнение интерпретатором или кэшируется и сохраняется на диске в файле .pyc:
Python compile.c, строка 1820
static PyCodeObject *
compiler_mod(struct compiler *c, mod_ty mod)
{
1 2 байта. — Примеч. ред.
2 https://docs.python.org/3/library/dis.html#python-bytecode-instructions.
Книги для программистов: https://t.me/booksforits
124 Компилятор
PyCodeObject *co; int addNone = 1;
static PyObject *module;
...
switch (mod->kind) { case Module_kind:
if (!compiler_body(c, mod->v.Module.body)) { compiler_exit_scope(c);
return 0;
}
break;
case Interactive_kind:
...
case Expression_kind:
...
...
co = assemble(c, addNone); compiler_exit_scope(c); return co;
}
compiler_body() перебирает все команды в модуле и посещает их:
Python compile.c, строка 1782
static int
compiler_body(struct compiler *c, asdl_seq *stmts)
{
int i = 0; stmt_ty st;
PyObject *docstring;
...
for (; i < asdl_seq_LEN(stmts); i++)
VISIT(c, stmt, (stmt_ty)asdl_seq_GET(stmts, i)); return 1;
}
Тип команды определяется вызовом asdl_seq_GET(), который проверяет тип узла AST.
Через макрос VISIT вызывает функцию для каждого типа команды из
Python compile.c:
#define VISIT(C, TYPE, V) {\
if (!compiler_visit_ ## TYPE((C), (V))) \ return 0; \
}
Книги для программистов: https://t.me/booksforits
Основная компиляция 125
Затем для stmt (общий тип команды) компилятор вызывает compiler_visit_ stmt(), после чего происходит переключение по всем возможным типам команд из Parser Python.asdl:
Python compile.c, строка 3375
static int
compiler_visit_stmt(struct compiler *c, stmt_ty s)
{
Py_ssize_t i, n;
/* Всегда присваивайте номер строки следующей инструкции для stmt */
SET_LOC(c, s); switch (s->kind) {
case FunctionDef_kind:
return compiler_function(c, s, 0); case ClassDef_kind:
return compiler_class(c, s);
...
case For_kind:
return compiler_for(c, s);
...
}
return 1;
}
Пример команды с for в Python:
for i in iterable:
# блок
else: # не обязательно, если iterable — False
# блок
Команду for можно наглядно представить на синтаксической диаграмме:
for |
exprlist |
in |
testlist |
|
: |
TYPE_COMMENT |
suite |
else |
: |
suite |
Если команда относится к типу for, то compiler_visit_stmt() вызывает compiler_for(). Эквивалентная функция compiler_*() существует для всех типов выражений и команд. Более тривиальные типы встраивают строки с инструкциями байт-кода, тогда как некоторые более сложные типы команд вызывают другие функции.
Книги для программистов: https://t.me/booksforits
126 Компилятор
Инструкции
У многих команд есть подкоманды. Цикл for имеет тело, но при присваивании и в итераторе также могут использоваться составные выражения.
Компилятор передает блоки с последовательностью инструкций состоянию компилятора. Структура данных инструкций содержит код операции, аргументы, целевой блок (для инструкции перехода; об этом ниже), а также номер строки команды.
Тип инструкции
Тип инструкции instr содержит следующие поля.
ПОЛЕ |
ТИП |
НАЗНАЧЕНИЕ |
i_jabs |
unsigned |
Флаг инструкции абсолютного перехода |
i_jrel |
unsigned |
Флаг инструкции относительного перехода |
i_lineno |
int |
Номер строки, для которой создается эта |
|
|
инструкция |
i_opcode |
unsigned char |
Код операции, представляемой инструкцией |
|
|
(см. Include Opcode.h) |
i_oparg |
int |
Аргумент кода операции |
i_target |
basicblock* |
Указатель на блок перехода из основного блока |
|
|
(basicblock), если значение i_jrel истинно |
Инструкции перехода
Инструкции перехода позволяют «перепрыгнуть» из одной инструкции в другую. Они делятся на абсолютные (безусловные) и относительные (условные).
Инструкции абсолютного перехода задают точный номер инструкции в скомпилированном объекте кода, а инструкции относительного перехода — позицию перехода относительно другой инструкции.
Книги для программистов: https://t.me/booksforits
Основная компиляция 127
Базовые блоки
Базовый блок (тип basicblock) содержит следующие поля.
ПОЛЕ |
ТИП |
НАЗНАЧЕНИЕ |
b_ialloc |
int |
Длина массива инструкций (b_instr) |
b_instr |
instr * |
Указатель на массив инструкций |
b_iused |
int |
Количество использованных инструкций |
|
|
(b_instr) |
b_list |
basicblock * |
Список блоков в данной единице компиляции |
|
|
(в обратном порядке) |
b_next |
basicblock* |
Указатель на следующий блок при обычном пото- |
|
|
ке управления |
b_offset |
int |
Смещение инструкции для блока, вычисляемое |
|
|
assemble_jump_offsets() |
b_return |
unsigned |
Истинно, если вставлен код операции |
|
|
RETURN_VALUE |
b_seen |
unsigned |
Используется для выполнения поиска в глубину |
|
|
по базовым блокам (см.«Ассемблер») |
b_startdepth |
int |
Глубина стека при входе в блок, вычисляемая |
|
|
stackdepth() |
Операции и аргументы
Разным типам операций требуются разные аргументы. Например, ADDOP_JREL и ADDOP_JABS представляют «операцию сложения с переходом к относительной позиции» и «операцию сложения с переходом к абсолютной позиции» соответственно.
Существуют и другие макросы: ADDOP_I вызывает функцию compiler_addop_i(), которая добавляет операцию с целочисленным аргументом. ADDOP_O вызывает функцию compiler_addop_o(), которая добавляет операцию с аргументом
PyObject.
Книги для программистов: https://t.me/booksforits