MicroPython:Основы/Язык MicroPython и его реализация/MicroPython-файлы формата «*.mpy»
MicroPython-файлы формата «*.mpy»[1]
В терминологии MicroPython файловый формат «*.mpy» – это формат двоичного контейнера, содержащего предварительно скомпилированный код, который можно импортировать как обычный «*.py» модуль. Файл foo.mpy можно импортировать при помощи import foo при условии, что механизм импорта может найти этот файл своим обычным способом. Обычно поиск по всем директориям в sys.path осуществляется по порядку. При поиске в конкретной директории сначала ищется файл foo.py, и если его не будет, то после этого ищется foo.mpy, и если его тоже не будет, его поиск продолжится в следующей директории. То есть у файла foo.py приоритет перед файлом foo.mpy.
Эти «*.mpy» файлы могут содержать байт-код, который обычно генерируется из файлов с исходным Python-кодом («*.py») при помощи программы mpy-cross. Кроме того, файл «*.mpy» может содержать нативный машинный код некоторых архитектур. Его можно сгенерировать множеством разных способов – в частности, из исходного C-кода.
Контроль версий и совместимость файлов «*.mpy»
Файл «*.mpy» не всегда бывает совместим с системой MicroPython. Совместимость зависит от следующих вещей:
- Версия файла «*.mpy». Система должна поддерживать версию файла, который она загружает.
- Функции байт-кода, используемые в файле «*.mpy». У файла и системы должны соответствовать две функции байт-кода: поддержка Юникода и инлайн-кэширование поиска в словарях байт-кода.
- Количество бит в малых целых числах. У файла «*.mpy» есть ограничение по минимальному количеству бит для малых целых чисел, и система, загружающая этот файл, должна поддерживать этот порог.
- Размер окна при QSTR-декомпрессии. У файла «*.mpy» есть ограничение по минимальному размеру окна при QSTR-декомпрессии, и у системы, загружающей этот файл, размер окна должен быть равен или выше этого порогового значения.
- Нативная архитектура. Если файл «*.mpy» содержит нативный машинный код, то в нем будет указана архитектура этого машинного кода, и система, загружающая этот файл, должна поддерживать выполнение кода этой архитектуры.
Если MicroPython-система поддерживает импорт файлов «*.mpy», то в ней будет поле sys.implementation.mpy, и она также вернет целое число, в котором будут зашифрованы версия (8 младших битов), функции и нативная архитектура системы.
Если импортируемый файл «*.mpy» провалит один из первых четырех тестов из списка выше, это выдаст ошибку ValueError('incompatible .mpy file'). Если импортируемый файл провалит тест на нативную архитектуру (если он содержит нативный машинный код), это выдаст ошибку ValueError('incompatible .mpy arch').
Если попытка импортировать файл «*.mpy» закончится неудачей, попробуйте следующее:
- Определите версию и флаги файла «*.mpy», поддерживаемые вашей MicroPython-системой, при помощи:
import sys
sys_mpy = sys.implementation.mpy
arch = [None, 'x86', 'x64',
'armv6', 'armv6m', 'armv7m', 'armv7em', 'armv7emsp', 'armv7emdp',
'xtensa', 'xtensawin'][sys_mpy >> 10]
print('mpy version:', sys_mpy & 0xff)
print('mpy flags:', end='')
if arch:
print(' -march=' + arch, end='')
if sys_mpy & 0x100:
print(' -mcache-lookup-bc', end='')
if not sys_mpy & 0x200:
print(' -mno-unicode', end='')
print()
- Проверьте валидность файла «*.mpy», проверив первые два его байта. В первом байте должна содержаться заглавная «M», а во втором байте должен быть номер версии, и он должен соответствовать версии MicroPython-системы. Если не соответствует, пересоберите свой «*.mpy» файл.
- Проверьте, соответствует ли версия системы в файле «*.mpy» версии кросс-компилятора mpy-cross, который использовался для сборки файла «*.mpy». Версию mpy-cross можно узнать при помощи mpy-cross --version. Если не соответствует, перекомпилируйте mpy-cross из Git-репозитория, найденного по метке (или хэшу) при помощи mpy-cross --version.
- Убедитесь, что используете правильные флаги mpy-cross. Их можно узнать при помощи кода выше или при помощи проверки Makefile-переменной MPY_CROSS_FLAGS на предмет используемого вами порта.
Таблица ниже показывает, как между собой соответствуют версии MicroPython и файла «*.mpy».
Версия MicroPython | Версия файла «*.mpy» |
---|---|
1.12 и выше | 5 |
1.11 | 4 |
1.9.3 – 1.10 | 3 |
1.9 – 1.9.2 | 2 |
1.5.1 – 1.8.7 | 0 |
А в этой таблице показаны Git-коммиты главного репозитория MicroPython, в которых менялась версия «*.mpy»:
Изменение версии «*.mpy» | Git-коммит |
---|---|
С 4 на 5 | 5716c5cf65e9b2cb46c2906f40302401bdd27517 |
С 3 на 4 | 9a5f92ea72754c01cc03e5efcdfe94021120531e |
Со 2 на 3 | ff93fd4f50321c6190e1659b19e64fef3045a484 |
С 1 на 2 | dd11af209d226b7d18d5148b239662e30ed60bad |
С 0 на 1 | 6a11048af1d01c78bdacddadd1b72dc7ba7c6478 |
Появление версии 0 | d8c834c95d506db979ec871417de90b7951edc30 |
Двоичная кодировка файлов «*.mpy»
MicroPython’овские файлы «*.mpy» – это контейнеры двоичных данных, в которых объекты кода хранятся в формате иерархии с вложениями (по типу матрешки). Чтобы эти файлы оставались маленькими, но по-прежнему могли хранить большой диапазон значений, в них повсеместно используется формат vuint (от англ. «variably-encoded-unsigned-integer», что можно перевести как «вариативно кодируемое беззнаковое целое число»). Как и в кодировке utf-8, в vuint-кодировке на каждый байт приходится 7 бит, а 8-ому (самому старшему) биту присваивается «1», если дальше следуют еще один или несколько байтов. То есть биты беззнакового целого числа в vuint – это младшие биты.
Высокоуровневая часть файла «*.mpy» состоит из двух элементов:
- Заголовок
- Исходный код во внешней области видимости. Этот код выполняется при импорте «*.mpy»
Заголовок
Элементы, из которых состоит заголовок файла «*.mpy»:
Формат | Содержание |
---|---|
byte | Значение «0x4d» («M» в кодировке ASCII) |
byte | Номер версии файла «*.mpy» |
byte | Флаги функций |
byte | Количество бит в малом целом числе |
vuint | Размер QSTR-окна |
Исходный код
В этом элементе содержится код – байт-код или нативный машинный код – но не только:
Формат | Содержание |
---|---|
vuint | Тип и размер |
... | Код (байт-код или машинный код) |
vuint | Количество объектов-констант |
vuint | Количество подэлементов исходного кода |
... | Объекты-константы |
... | Подэлементы исходного кода |
В первом vuint закодирован тип хранящегося тут кода (два младших бита) и размер кода в несжатом состоянии (количество RAM-памяти, где хранится этот код).
Дальше идет сам код. Если это байт-код, тут также хранятся сжатые QSTR-значения.
Дальше идут два vuint с информацией о количестве объектов-констант и подэлементов исходного кода.
Дальше хранятся объекты-константы.
Наконец, в конце хранятся подэлементы исходного кода (в обратном порядке).