Это маленький полуобучающий проект о том, как пользоваться PyPy на примере написания интерпрeтатора для языкa brainfuck. Здесь мы напишем и проведем последовательное улучшение интерпретатора на Питоне. С более подробной информацией можно ознакомится в статье на Хабре а исходный код в репозитории на Github. Тут мы просто обобщаем и автоматизируем при помощи скриптов выполнение всех процессов и тестов. В качестве результата мы получим копилятор, который будет переводить код brainfuck в машинный код.
Для начала нам понадобится python3, pypy2.7 (с необходимыми модулями) и hg.
В начале работы с проектом выполните fist-start, чтобы установить все необходимое. ВНИМАНИЕ: требуется пароль администратора!
После выполнения установки проверьте корректность, выполнив:
python3 --version
pypy2.7 --version
Основные скрипты будут написаны в Makefile
Для начала написшем интерпретатор самостоятельно и запустим его с помощью питона.
Если вам интересен код - можете заглянуть в папку source. Программы на brainfuck лежат в папке brainfuck, здесь мы запустим first_brainfuck.b. Сейчас мы будем использовать файл python_only.py. Следующая команда:
make python_only
После начнется падение 99 бутылок
Теперь перейдем к PyPy. Нам понадобится RPython, мы скачаем репозиторий с помощью следующей команды:
make install_pypy_rpython
Далее начнем трансляцию интерпретатора следующей командой
make simple_pypy
В результате мы получим папку build, в которой лежит наш интерпретатор, запустим его с программой, выводящей фрактал
cd build
time ./simple_pypy-c ../brainfuck/mandel.b
cd ..
Продолжим улучшение, воспользуемся фичей Pypy и добавим компилятор времени выполнения JIT. Для этого надо добавить несколько подсказок как в сам интерпретатор (сейчас это файл jit_pypy.py) и флагом для rpython
make jit_pypy
Он заметно дольше транслируется. Можно проверить результат слудующим образом:
cd build
time ./jit_pypy-c ../brainfuck/mandel.b
cd ..
Будет выводиться фрактал, даже несколько быстрее чем в предыдущем случае.
Добавим минорную оптимизацию. Мы скажем компилятору что dict не меняется в процессе использования его внутри mainloop.
make opt_jit_pypy
Ну и проверить это можно следующим способом:
cd build
time ./opt_jit_pypy-c ../brainfuck/mandel.b
cd ..
Теперь мы имеем 3 интерпретатора в папке build
Ну как можно обойтись без инструмента дебага, позволяющего нам видеть все необходимое. Здесь мы проапдейтим наш интрепретатор так, чтобы он при соблюдении определненых условий выводил отладочную информацию. Необходимые изменения можно найли в файле log_jit_pypy.py. Команда для вызова:
make log_jit_pypy
Теперь нам нужно установить переменную PYPYLOG для того, чтобы выводить определенные логи в определенный файл
export PYPYLOG=jit-log-opt:logfile
далее запускаем программу, тут мы будем пользоваться тестовой маленькой программой
cd build
./log_jit_pypy-c ../brainfuck/bench.b
cat logfile
Теперь мы можем анализировать результат работы программы с логами
Установим переменные среды.
export PYPYLOG=jit-backend-dump:log
export PYTHONPATH=`pwd`/pypy
Далле сгенерируем транслятор
make log_jit_pypy
Запускаем его
cd build
./log_jit_pypy-c ../brainfuck/mandel.b
Появится файл log, теперь можно сделать viewcode.py
cd ..
pypy2.7 pypy/rpython/jit/backend/tool/viewcode.py build/log
Или вы можетно запустить скрипт
make viewcode
Данный раздел пока что использует отдельный код, для демонстрации работы вызова с-функии из динамической библеотеке.
Для начала необходимо сокмпилировать дин библеотеку с одной функцией:
void hello() {
printf("Hello world!\n")
}
Это можно сделать следующим образом
cd source/csource
make
cd ../..
Далее запустим скрипт
make call_c_fun
Теперь в языке добавлены новые символы
- 'h' - выводит строку "Hello, world"
- 'p' - выводит значение переменной 'position'
- 'w' - выводит значение 42 (ответ на вселенский вопрос и вообще)
Если необходило скомпелировать и вызвать выполнение тестового скрипта, используйте следующую команду
make play_hellofun
Если в добавок необходимо еще и просмотреть дамп jit, используйте следющую команду
make viewcode_withfun
Если вы уже скомпилировали интерпритатор, то просто запустить работу программы можно следующей коммандой
make play_hellofun_only
В этой части мы запускаем микротесты, направленные на тестироване времени работы определенных частей бэнчмарков.
Модули, которые неоходимо скачасть для работы с тестами:
- sympy
- pyperfomance
- Вынести все файлы микротестов в отедьную директорию, лучше всего при помощи hardlinks.
- В тестах ввести полный путь до необходимых файлов данных.
- В случае если необходимо будет запустить дамп с профайлетами, вополнить два предыдущих пункта и для этих файлов.
- Желательно оставить папку для логов.
Для их запуска микротестов следует использовать следующую команду
./test-jit-dump.sh path/to/test/root
Важно чтобы <path/to/test/root> имел следующее дерево
root
|_ test1
| |_ test-one.py
|_ test2
| |_ test-two.py
...
Тогда скрипт отработает корректно.
Если вы используете основной репозиторий для выполнения этого скрипта можно запустить скрипт без аргумента. В скрипт встроен дефолтный путь.
Что делает скпипт? Он проходится по всем тестам вида test-<name>.py и записывает логи в файл jit-backend-<name>.dump. После полной записи, он перекладывает логи в директорию microtest-logs
Далее логи можно прогнать через codeview.py который выведем дизаасембл-код. Для этого следует использовать jit-view.sh, в качестве аргумента следует подавать сам файл лога с раширением .dump