Основная цель данного проекта - создание улучшенной версии Pawn 3.2 с исправлениями и нововведениями как для компилятора, так и для библиотеки времени выполнения.
Кодовая база проекта базируется на форке Zeex'а (Pawn 3.10), направленном на исправление багов и улучшение компилятора для SA-MP.
Данный проект, напротив, сфокусирован на улучшении интерпретатора и модулей расширения AMX - часть изменений перенесена из более поздних версий Pawn (такие как, например, добавление функций frename(), fstat() и strcopy()), другая часть является оригинальной разработкой (верификация байткода при загрузке скрипта, модернизированное ядро интерпретатора).
Список изменений:
- Бэкпортированы изменения из Pawn 3.3 и дальнейших предрелизных версий, вплоть до 4.0.
Среди перенесённых изменений можно отметить добавление новых нативных функций в модулях amxfile (frename, fstat, fattrib и filecrc), amxtime (settimestamp() и cvttimestamp()) и amxstring (strcopy).
Кроме новых функций есть исправления для уже существующих - valstr (устранён уход в бесконечный цикл при больших числах), printf (исправлена неправильная обработка комбинации "%%").
Перенести изменений из 4.0 и более поздних версий не представляется возможным, т.к. это повлечёт нарушение авторских прав - начиная с 4-й версии, основная часть кода Pawn была переведена под новую лицензию.
- В корневой папке репозитория добавлен проект для CMake, служащий для построения и компилятора, и интерпретатора.
Также в скрипт для построения рантайма (в папке "source/amx") добавлены опции для построения с 32/64-битными ячейками, компиляции модулей расширения в виде статический библиотек (может пригодиться для встраивания модулей в исполняемый файл пользовательского приложения, как в сервере SA-MP), и включения/исключения отдельных модулей из билда.
- Добавлено новое ядро интерпретатора (amx_Exec), основанное на двух других стандартных ядрах: обычном (ANSI C) и GCC-специфичном.
Этот шаг должен значительно упростить поддержку кода - сопровождать одну версию ядра куда проще, чем две и более.
Также в новое ядро добавлены новые проверки времени выполнения, исключающие множество недочётов и уязвимостей, присутствующих в оригинальном коде.
Например, в оригинале во многих опкодах, совершающих чтение или запись в секцию данных или стек, отсутствует проверка адреса доступа, благодаря чему атакующий может читать и записывать данные за пределами памяти скрипта.
Кроме того, у всех опкодов, изменяющих состояние стека, кроме опкода STACK, отсутствует проверка значение в регистре STK (указателе вершины стека) - можно с помощью SCTRL 4 заставить STK указывать за пределы памяти скрипта и выполнять чтение/запись данных с помощью опкодов PUSH*/POP*.
В новом ядре эти и многие другие виды уязвимостей устранены. Тем не менее, для совместимости оставлена возможность использования старых ядер, для этого следует отключить опцию PAWN_USE_NEW_AMXEXEC в проекте для CMake.
Кроме того, в новом ядре изменён принцип считывания аргументов в опкодах, интерпретатор избавлен от последовательного чтения. В оригинале чтобы считать 2-й аргумент, нужно сначала прочитать 1-й, а чтобы перейти к следующему опкоду, нужно считать все аргументы текущего опкода. Теперь же интерпретатор может считывать аргументы не по порядку и при возможности вообще пропускать их. Например, в инструкциях jump и j** интерпретатор не будет считывать адрес перехода, если условие перехода не выполняется. Кроме того, интерпретатор теперь может считывать два аргумента не друг за другом, а одновременно (при условии, что операции чтения из памяти распараллеливаются на целевом процессоре).
Сам процесс считывания аргумента теперь не модифицирует значение в регистре cip, поэтому в случае возникновения ошибки интерпретатор сможет выдать адрес именно инструкции, на которой возникла ошибка, а не адрес одного из аргументов инструкции (или даже следующей инструкции, если у текущей аргументов нет), как в оригинале.
Несмотря на добавление новых проверок времени выполнения, новое ядро обладает большей производительностью, чем у стандартных ядер.
Достигнуто это благодаря упрощению кода (см. пример выше с устранением последовательности чтения) и использованию нестандартных возможностей компиляторов C/C++ для статического предсказания переходов (__assume(0) в MSVC++, а также __builtin_unreachable() и __builtin_expect() в GCC и Clang).
На моём ПК прирост производительности при построении в VS 2010 составил 23%, VS 2017 - 75%, MinGW - 90%. Я также пробовал провести измерения на своём планшете (ARMv8) - на нём разница оказалась не сильно заметна: +10% при компиляции с GCC и -15% с Clang.
Если вы хотите проверить факт прироста производительности самостоятельно, постройте два экземпляра интерпретатора, с включенной и выключенной опцией PAWN_USE_NEW_AMXEXEC соответственно, затем скомпилируйте скрипт fib_bench.p (находится в папке "examples") и запустите его на каждом из них.
- Помимо проверок времени выполнения добавлена верификация байткода при загрузке скрипта (в amx_Init).
- Устранена зависимость размера ячейки Pawn от размера указателя на целевой платформе.
Теперь можно использовать интерпретатор с 32-битными ячейками под управлением 64-разрядной ОС.
В оригинальной версии Pawn при такой конфигурации работа интерпретатора невозможна, т.к. физические адреса нативных функций и хендлов модулей расширения AMX хранятся в ячейках, а размера ячейки (32 бита) с большой долей вероятности может не хватить для хранения 64-битного указателя.
В данном форке если размер указателя больше размера ячейки, то под физические адреса (указатели) выделяется отдельный блок памяти, в котором они помещаются полностью.
- Файловые функции теперь оперируют над ID файлов (например, 1, 2, 3, ...) вместо указателей FILE *, приведённых к типу ячейки.
Это означает, что файловые функции не обрушат целое хост-приложение из-за нечаянного использования неправильного ID файла (привет, SA-MP) (например, при вызове функции fwrite() после закрытия файла с помощью fclose()), т.к. ID легко проверить на валидность.
- В инструкциях доступа к секциям данных, в которых адрес доступа указывается в виде константы (LOAD.pri/alt, LREF.pri/alt, STOR.pri/alt и т.д.), реализована релокация адресов.
Теперь при загрузке скрипта виртуальные адреса в вышеупомянутых инструкциях заменяются физическими адресами для прямого доступа к данным, что должно повысить общую производительность. Релокация осуществляется только при использовании нового ядра интерпретатора (см. выше) и только если ячейка Pawn способна вместить физический адрес.
Исходный код: https://github.com/Daniel-Cortez/pawn-3.2-plus
Статью подготовил: Daniel_Cortez
Специально для Pro-Pawn.ruЗапрещается копирование содержимого данной статьи на других ресурсах без разрешения автора.