MicroPython .mpy файли¶
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 для використання на стороні хоста без повного checkout мікропрограми:
$ 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зберігає перевірки (assertions) та повні дані про розташування в джерелі;3прибирає перевірки та рядки документації і переписує блоки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“ у кодуванні ASCII, а другий байт — номером версії, який повинен збігатися з версією системи, отриманою вище. Якщо вони не збігаються — перезберіть .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 to 6.3 |
bdbc869f9ea200c0d28b2bc7bfb60acd9d884e1b |
6.1 to 6.2 |
6967ff3c581a66f73e9f3d78975f47528db39980 |
6 to 6.1 |
d94141e1473aebae0d3c63aeaa8397651ad6fa01 |
5 to 6 |
f2040bfc7ee033e48acef9f289790f3b4e6b74e5 |
4 to 5 |
5716c5cf65e9b2cb46c2906f40302401bdd27517 |
3 to 4 |
9a5f92ea72754c01cc03e5efcdfe94021120531e |
2 to 3 |
ff93fd4f50321c6190e1659b19e64fef3045a484 |
1 to 2 |
dd11af209d226b7d18d5148b239662e30ed60bad |
0 to 1 |
6a11048af1d01c78bdacddadd1b72dc7ba7c6478 |
початкова версія 0 |
d8c834c95d506db979ec871417de90b7951edc30 |
Двійкове кодування .mpy файлів¶
MicroPython .mpy файли — це двійковий контейнерний формат з об’єктами коду (байт-кодом та машинним кодом для конкретної архітектури), що зберігаються всередині у вкладеній ієрархії. Код зовнішнього модуля зберігається першим, а потім йдуть його дочірні елементи. Кожен дочірній елемент може мати власні дочірні елементи, наприклад, у випадку класу з методами або функції, що визначає лямбда-вираз чи вираз-генератор. Щоб зберігати файли невеликими, при цьому забезпечуючи широкий діапазон можливих значень, у багатьох місцях використовується концепція цілого числа зі змінним кодуванням (vuint). Подібно до кодування UTF-8, це кодування зберігає 7 бітів на байт, де 8-й біт (MSB) встановлюється, якщо за ним іде один або більше байтів. Біти беззнакового цілого числа зберігаються у vuint у вигляді LSB.
Верхній рівень .mpy файлу складається з трьох частин:
Заголовок.
Глобальні таблиці qstr та констант.
Сирий код зовнішньої області видимості модуля. Ця зовнішня область видимості виконується під час імпорту .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, що підраховує кількість дочірніх елементів сирого коду.
Нарешті, всі дочірні елементи сирого коду зберігаються рекурсивно.