Файлы .mpy в MicroPython¶
MicroPython вводит понятие файла .mpy — это двоичный контейнерный формат файла, содержащий предварительно скомпилированный код, который можно импортировать как обычный модуль .py. Файл foo.mpy можно импортировать с помощью import foo, если foo.mpy может быть найден обычным способом механизмом импорта. Как правило, каждый каталог, перечисленный в sys.path, просматривается по порядку. При просмотре конкретного каталога сначала ищется foo.py, и если он не найден, то ищется foo.mpy, после чего поиск продолжается в следующем каталоге, если ни один из них не найден. Таким образом, foo.py имеет приоритет над foo.mpy.
Эти файлы .mpy могут содержать bytecode, который обычно генерируется из исходных файлов Python (файлов .py) с помощью программы mpy-cross. Для некоторых архитектур файл .mpy может также содержать машинный код, который может быть сгенерирован различными способами, в первую очередь из исходного кода на C.
Компилятор mpy-cross¶
mpy-cross — это кросс-компилятор, который превращает исходный файл .py в двоичный контейнер .mpy, готовый к импорту на камере. Он входит в состав дерева исходного кода MicroPython (того же, что используется для сборки прошивки камеры) и также публикуется как пакет pip для использования на стороне хоста без полной выгрузки исходного кода прошивки:
$ pip install --user mpy-cross
Или через pipx:
$ pipx install mpy-cross
После установки запустите его для одного исходного файла:
$ mpy-cross foo.py
В результате в текущем каталоге создаётся foo.mpy, готовый к копированию в файловую систему камеры рядом с другими модулями или к добавлению в образ ROMFS.
Наиболее полезные параметры командной строки:
-o <path>— путь вывода для сгенерированного.mpy(по умолчанию совпадает с именем входного файла с заменённым расширением;-o -выводит в stdout).-O<n>— уровень оптимизации от0до3. По умолчанию0сохраняет проверки assert и полную информацию о расположении в исходном коде;3удаляет проверки assert и строки документации и переписывает блокиif __debug__. Этот уровень управляет той же настройкойmicropython.opt_level, которую предоставляет среда выполнения.-march=<arch>— целевая нативная архитектура для функций, помеченных декораторами@nativeи@viper. Требуется, когда в исходном коде используются эти декораторы. Значение должно соответствовать классу MCU камеры: выберите его из списка, который выводитmpy-cross --help, или считайте его с камеры во время выполнения с помощьюsys.implementation._mpy.-s <path>— строка пути к исходному коду, встроенная в отладочную информацию.mpy. Полезна, когда путь на диске отличается от пути импорта, под которым файл должен отображаться в трассировках стека.-X emit=bytecode|native|viper— выбор стандартного эмиттера для всего модуля (альтернатива на уровне модуля для декораторов@native/@viperна уровне функций).--version— выводит версию формата.mpy, который генерирует этот бинарный файл. Это число должно совпадать с версией, поддерживаемой средой выполнения камеры (см. таблицу версий ниже), иначе при импорте возникнетValueError('incompatible .mpy file').
Запустите mpy-cross --help, чтобы получить полный список флагов.
Пакет pip также предоставляет небольшой API модуля Python, чтобы сценарии сборки могли управлять компилятором внутри процесса, а не вручную создавать подпроцесс:
import mpy_cross
mpy_cross.compile('foo.py', dest='build/foo.mpy', opt=3,
march=mpy_cross.NATIVE_ARCH_ARMV7EMSP)
mpy_cross.compile, mpy_cross.run и mpy_cross.mpy_version — это три точки входа; mpy_cross.CrossCompileError несёт stderr компилятора, когда что-то идёт не так. Константы архитектуры (NATIVE_ARCH_ARMV7EMSP, NATIVE_ARCH_ARMV7EMDP и т. д.) соответствуют строкам, которые принимает флаг -march.
Версионирование и совместимость файлов .mpy¶
Данный файл .mpy может быть совместим или несовместим с данной системой MicroPython. Совместимость основана на следующем:
Версия файла .mpy: версия файла должна соответствовать версии, поддерживаемой загружающей его системой.
Подверсия файла .mpy: если файл .mpy содержит нативный машинный код, то подверсия файла должна соответствовать версии, поддерживаемой загружающей его системой. В противном случае, если в файле .mpy нет нативного машинного кода, подверсия при загрузке игнорируется.
Биты малого целого числа: файл .mpy требует минимального количества битов в small integer, и загружающая его система должна поддерживать не меньше этого количества битов.
Нативная архитектура: если файл .mpy содержит нативный машинный код, то он указывает архитектуру этого машинного кода, и загружающая его система должна поддерживать выполнение кода для этой архитектуры.
Если система MicroPython поддерживает импорт файлов .mpy, то будет существовать поле sys.implementation._mpy, возвращающее целое число, которое кодирует версию (младшие 8 бит), функции и нативную архитектуру.
Попытка импортировать файл .mpy, который не проходит один из первых четырёх тестов, вызовет ValueError('incompatible .mpy file'). Попытка импортировать файл .mpy, который не проходит тест на нативную архитектуру (если он содержит нативный машинный код), вызовет 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', 'rv32imc', 'rv64imc'][(sys_mpy >> 10) & 0x0F] print('mpy version:', sys_mpy & 0xff) print('mpy sub-version:', sys_mpy >> 8 & 3) print('mpy flags:', end='') if arch: print(' -march=' + arch, end='') if (sys_mpy >> 16) != 0: print(' -march-flags=' + (sys_mpy >> 16), end='') print()
Проверьте корректность файла .mpy, изучив первые два байта файла. Первый байт должен быть заглавной буквой „M“, а второй байт будет номером версии, который должен совпадать с версией системы, указанной выше. Если он не совпадает, пересоберите файл .mpy.
Проверьте, совпадает ли версия .mpy системы с версией, генерируемой
mpy-cross, который использовался для сборки файла .mpy, что определяется командойmpy-cross --version. Если она не совпадает, перекомпилируйтеmpy-crossиз репозитория Git, выгруженного на теге (или хэше), о котором сообщаетmpy-cross --version.Убедитесь, что вы используете правильные флаги
mpy-cross, найденные с помощью приведённого выше кода или путём изучения переменной MakefileMPY_CROSS_FLAGSдля используемого вами порта.Если в третьем байте файла .mpy установлен бит #6, то проверьте, совместимы ли закодированные архитектурно-зависимые флаговые биты vuint с целевой системой, на которую вы импортируете файл.
В следующей таблице показано соответствие между выпуском MicroPython и версией .mpy.
Выпуск MicroPython |
версия .mpy |
|---|---|
v1.23.0 и выше |
6.3 |
v1.22.x |
6.2 |
v1.20 - v1.21.0 |
6.1 |
v1.19.x |
6 |
v1.12 - v1.18 |
5 |
v1.11 |
4 |
v1.9.3 - v1.10 |
3 |
v1.9 - v1.9.2 |
2 |
v1.5.1 - v1.8.7 |
0 |
Для полноты картины в следующей таблице показан коммит Git основного репозитория MicroPython, в котором была изменена версия .mpy.
изменение версии .mpy |
Коммит Git |
|---|---|
с 6.2 на 6.3 |
bdbc869f9ea200c0d28b2bc7bfb60acd9d884e1b |
с 6.1 на 6.2 |
6967ff3c581a66f73e9f3d78975f47528db39980 |
с 6 на 6.1 |
d94141e1473aebae0d3c63aeaa8397651ad6fa01 |
с 5 на 6 |
f2040bfc7ee033e48acef9f289790f3b4e6b74e5 |
с 4 на 5 |
5716c5cf65e9b2cb46c2906f40302401bdd27517 |
с 3 на 4 |
9a5f92ea72754c01cc03e5efcdfe94021120531e |
с 2 на 3 |
ff93fd4f50321c6190e1659b19e64fef3045a484 |
с 1 на 2 |
dd11af209d226b7d18d5148b239662e30ed60bad |
с 0 на 1 |
6a11048af1d01c78bdacddadd1b72dc7ba7c6478 |
начальная версия 0 |
d8c834c95d506db979ec871417de90b7951edc30 |
Двоичное кодирование файлов .mpy¶
Файлы .mpy в MicroPython представляют собой двоичный контейнерный формат с объектами кода (байт-кодом и нативным машинным кодом), хранящимися внутри во вложенной иерархии. Сначала хранится код внешнего модуля, а затем следуют его потомки. Каждый потомок может иметь дальнейших потомков, например, в случае класса, имеющего методы, или функции, определяющей лямбду или включение (comprehension). Чтобы файлы оставались небольшими, обеспечивая при этом широкий диапазон возможных значений, во многих местах используется концепция целого числа без знака с переменной длиной кодирования (vuint). Подобно кодировке UTF-8, эта кодировка хранит 7 бит на байт, при этом 8-й бит (MSB) устанавливается, если следует один или более байтов. Биты целого числа без знака хранятся в vuint в форме от младшего к старшему (LSB).
Верхний уровень файла .mpy состоит из трёх частей:
Заголовок.
Глобальные таблицы qstr и констант.
Сырой код (raw-code) для внешней области видимости модуля. Эта внешняя область видимости выполняется при импорте файла .mpy.
Вы можете изучить содержимое файла .mpy с помощью mpy-tool.py, например (запуск из корня основного репозитория MicroPython):
$ ./tools/mpy-tool.py -xd myfile.mpy
Заголовок¶
Заголовок .mpy:
размер |
поле |
|---|---|
байт |
значение 0x4d (ASCII „M“) |
байт |
основной номер версии .mpy |
байт |
флаги функций, нативная архитектура, дополнительный номер версии (в более старых версиях были флаги функций) |
байт |
количество битов в малом целом числе |
Третий байт разбит следующим образом (сначала MSB):
бит |
значение |
|---|---|
7 |
зарезервировано, должно быть 0 |
6 |
за заголовком следует архитектурно-зависимый vuint флагов |
5..2 |
номер нативной архитектуры |
1..0 |
дополнительный номер версии |
Архитектурно-зависимые флаги¶
Если бит #6 байта флагов функций заголовка установлен, то за заголовком будет следовать vuint, содержащий необязательную архитектурно-зависимую информацию. Содержимое этого целого числа зависит от того, для какой нативной архитектуры предназначен файл.
В настоящее время это используется для хранения информации о том, какие расширения процессора RISC-V нужны файлу MPY для корректной работы помимо I, M, C и Zicsr. Различные разновидности ArmV7 идентифицируются по их номеру нативной архитектуры, но повторное использование этого механизма усложнило бы ситуацию для RV32 и RV64.
Файлы MPY, нацеленные на RV32 или RV64, которым не нужны какие-либо особые расширения процессора, не должны предоставлять целое число флагов (наряду с установкой соответствующего бита в заголовке). Отсутствие значения флагов для файлов MPY RV32 и RV64 используется для указания того, что никакие специфические расширения не нужны, и экономит один байт в итоговом двоичном файле.
См. также параметр командной строки -march-flags в mpy-tool.py и mpy-cross, а также параметр командной строки --arch-flags в mpy_ld.py для установки этого значения при создании файлов MPY.
Глобальные таблицы qstr и констант¶
Файл .mpy содержит одну таблицу qstr и одну таблицу объектов констант. Они являются глобальными для файла .mpy и на них ссылаются все вложенные объекты сырого кода. Таблица qstr сопоставляет внутренний номер qstr (внутренний для файла .mpy) с разрешённым номером qstr среды выполнения, в которую импортируется файл .mpy. Это связывает файл .mpy с остальной частью системы, внутри которой он выполняется. Таблица объектов констант заполняется ссылками на все объекты констант, которые нужны файлу .mpy.
размер |
поле |
|---|---|
vuint |
количество qstr |
vuint |
количество объектов констант |
… |
данные qstr |
… |
закодированные объекты констант |
Элементы сырого кода¶
Элемент сырого кода содержит код, либо байт-код, либо нативный машинный код. Его содержимое:
размер |
поле |
|---|---|
vuint |
тип, размер и наличие вложенных элементов сырого кода |
… |
код (байт-код или машинный код) |
vuint |
количество вложенных элементов сырого кода (только если не равно нулю) |
… |
вложенные элементы сырого кода |
Первый vuint в элементе сырого кода кодирует тип кода, хранящегося в этом элементе (два младших значащих бита), наличие у этого сырого кода потомков (третий младший значащий бит) и длину следующего за ним кода (объём RAM, выделяемый для него).
За vuint следует сам код. Если только тип кода не является кодом viper с перемещениями, этот код представляет собой постоянные данные и не нуждается в изменении.
Если у этого сырого кода есть потомки (на что указывает бит в первом vuint), то за кодом следует vuint, подсчитывающий количество вложенных элементов сырого кода.
Наконец, любые вложенные элементы сырого кода сохраняются рекурсивно.