MicroPython .mpy-bestanden¶
MicroPython definieert het concept van een .mpy-bestand, een binair containerbestandsformaat dat voorgecompileerde code bevat en dat kan worden geïmporteerd zoals een normale .py-module. Het bestand foo.mpy kan worden geïmporteerd via import foo, zolang foo.mpy op de gebruikelijke manier door het importmechanisme kan worden gevonden. Gewoonlijk wordt elke map die in sys.path is vermeld op volgorde doorzocht. Bij het doorzoeken van een bepaalde map wordt eerst naar foo.py gezocht en als dat niet wordt gevonden, wordt naar foo.mpy gezocht; vervolgens gaat het zoeken verder in de volgende map als geen van beide is gevonden. Daardoor krijgt foo.py voorrang boven foo.mpy.
Deze .mpy-bestanden kunnen bytecode bevatten die meestal wordt gegenereerd uit Python-bronbestanden (.py-bestanden) via het programma mpy-cross. Voor sommige architecturen kan een .mpy-bestand ook native machinecode bevatten, die op uiteenlopende manieren kan worden gegenereerd, met name uit C-broncode.
De mpy-cross-compiler¶
mpy-cross is de cross-compiler die een .py-bronbestand omzet in een .mpy-binaire container die klaar is om op de cam te importeren. Het maakt deel uit van de MicroPython-broncode (dezelfde die wordt gebruikt om de firmware van de cam te bouwen) en wordt ook gepubliceerd als pip-pakket voor gebruik aan de hostzijde zonder een volledige firmware-checkout:
$ pip install --user mpy-cross
Of via pipx:
$ pipx install mpy-cross
Roep het na installatie aan op een enkel bronbestand:
$ mpy-cross foo.py
Dit produceert foo.mpy in de huidige map, klaar om naar het bestandssysteem van de cam te kopiëren naast andere modules of om in te voeren in een ROMFS-image.
De nuttigste opdrachtregelopties:
-o <path>– uitvoerpad voor de gegenereerde.mpy(standaard de invoerbestandsnaam met de extensie vervangen;-o -schrijft naar stdout).-O<n>– optimalisatieniveau0tot3. De standaardwaarde0behoudt asserties en volledige bronlocaties;3verwijdert asserties en docstrings en herschrijftif __debug__-blokken. Het niveau bepaalt hetzelfdemicropython.opt_level-oppervlak dat de runtime blootstelt.-march=<arch>– doel-native-architectuur voor functies met de decorators@nativeen@viper. Vereist wanneer de broncode die decorators gebruikt. De waarde moet overeenkomen met de MCU-klasse van de cam: kies deze uit de lijst diempy-cross --helpafdrukt, of lees deze op runtime af van de cam metsys.implementation._mpy.-s <path>– broncode-pad-tekenreeks die is ingebed in de debug-informatie van de.mpy. Nuttig wanneer het pad op schijf verschilt van het importpad waaronder het bestand in tracebacks moet verschijnen.-X emit=bytecode|native|viper– kies de standaard-emitter voor de hele module (een alternatief per functie voor de decorators@native/@viper).--version– drukt de.mpy-formaatversie af die deze binary uitstoot. Dat nummer moet overeenkomen met de versie die de runtime van de cam ondersteunt (zie de release-tabel hieronder), anders zal de importValueError('incompatible .mpy file')opwerpen.
Voer mpy-cross --help uit voor de volledige lijst met vlaggen.
Het pip-pakket biedt ook een kleine Python-module-API zodat buildscripts de compiler in-process kunnen aansturen in plaats van handmatig een subproces te forken:
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 en mpy_cross.mpy_version zijn de drie ingangspunten; mpy_cross.CrossCompileError draagt de stderr van de compiler mee wanneer er iets misgaat. De architectuurconstanten (NATIVE_ARCH_ARMV7EMSP, NATIVE_ARCH_ARMV7EMDP, enz.) komen overeen met de tekenreeksen die de -march-vlag accepteert.
Versiebeheer en compatibiliteit van .mpy-bestanden¶
Een bepaald .mpy-bestand kan al dan niet compatibel zijn met een bepaald MicroPython-systeem. Compatibiliteit is gebaseerd op het volgende:
Versie van het .mpy-bestand: de versie van het bestand moet overeenkomen met de versie die wordt ondersteund door het systeem dat het laadt.
Subversie van het .mpy-bestand: als het .mpy-bestand native machinecode bevat, dan moet de subversie van het bestand overeenkomen met de versie die wordt ondersteund door het systeem dat het laadt. Anders, als er geen native machinecode in het .mpy-bestand zit, wordt de subversie genegeerd bij het laden.
Small-integer-bits: het .mpy-bestand vereist een minimumaantal bits in een small integer en het systeem dat het laadt moet ten minste zoveel bits ondersteunen.
Native architectuur: als het .mpy-bestand native machinecode bevat, dan specificeert het de architectuur van die machinecode en het systeem dat het laadt moet de uitvoering van de code van die architectuur ondersteunen.
Als een MicroPython-systeem het importeren van .mpy-bestanden ondersteunt, dan bestaat het veld sys.implementation._mpy en retourneert het een geheel getal dat de versie (onderste 8 bits), functies en native architectuur codeert.
Het proberen te importeren van een .mpy-bestand dat een van de eerste vier tests niet doorstaat, zal ValueError('incompatible .mpy file') opwerpen. Het proberen te importeren van een .mpy-bestand dat de native-architectuurtest niet doorstaat (als het native machinecode bevat) zal ValueError('incompatible .mpy arch') opwerpen.
Als het importeren van een .mpy-bestand mislukt, probeer dan het volgende:
Bepaal de .mpy-versie en -vlaggen die door uw MicroPython-systeem worden ondersteund door het volgende uit te voeren:
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()
Controleer de geldigheid van het .mpy-bestand door de eerste twee bytes van het bestand te inspecteren. De eerste byte moet een hoofdletter ‘M’ zijn en de tweede byte is het versienummer, dat moet overeenkomen met de systeemversie van hierboven. Als het niet overeenkomt, bouw dan het .mpy-bestand opnieuw.
Controleer of de .mpy-versie van het systeem overeenkomt met de versie die wordt uitgestoten door de
mpy-crosswaarmee het .mpy-bestand is gebouwd, te vinden metmpy-cross --version. Als het niet overeenkomt, hercompileer danmpy-crossvanuit de Git-repository uitgecheckt op de tag (of hash) die doormpy-cross --versionwordt gerapporteerd.Zorg ervoor dat u de juiste
mpy-cross-vlaggen gebruikt, te vinden met de bovenstaande code of door de Makefile-variabeleMPY_CROSS_FLAGSte inspecteren voor de port die u gebruikt.Als de derde byte van het .mpy-bestand bit #6 gezet heeft, controleer dan of de gecodeerde architectuurspecifieke vlag-bits-vuint compatibel is met het doel waarop u het bestand importeert.
De volgende tabel toont de overeenkomst tussen MicroPython-release en .mpy-versie.
MicroPython-release |
.mpy-versie |
|---|---|
v1.23.0 en hoger |
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 |
Voor de volledigheid toont de volgende tabel de Git-commit van de hoofd-MicroPython-repository waarbij de .mpy-versie is gewijzigd.
.mpy-versiewijziging |
Git-commit |
|---|---|
6.2 naar 6.3 |
bdbc869f9ea200c0d28b2bc7bfb60acd9d884e1b |
6.1 naar 6.2 |
6967ff3c581a66f73e9f3d78975f47528db39980 |
6 naar 6.1 |
d94141e1473aebae0d3c63aeaa8397651ad6fa01 |
5 naar 6 |
f2040bfc7ee033e48acef9f289790f3b4e6b74e5 |
4 naar 5 |
5716c5cf65e9b2cb46c2906f40302401bdd27517 |
3 naar 4 |
9a5f92ea72754c01cc03e5efcdfe94021120531e |
2 naar 3 |
ff93fd4f50321c6190e1659b19e64fef3045a484 |
1 naar 2 |
dd11af209d226b7d18d5148b239662e30ed60bad |
0 naar 1 |
6a11048af1d01c78bdacddadd1b72dc7ba7c6478 |
initiële versie 0 |
d8c834c95d506db979ec871417de90b7951edc30 |
Binaire codering van .mpy-bestanden¶
MicroPython .mpy-bestanden zijn een binair containerformaat met code-objecten (bytecode en native machinecode) die intern in een geneste hiërarchie worden opgeslagen. De code voor de buitenste module wordt eerst opgeslagen en daarna volgen de onderliggende objecten. Elk onderliggend object kan verdere onderliggende objecten hebben, bijvoorbeeld in het geval van een klasse met methoden, of een functie die een lambda of comprehension definieert. Om bestanden klein te houden en toch een groot bereik aan mogelijke waarden te bieden, gebruikt het op veel plaatsen het concept van een variabel-gecodeerd-niet-ondertekend-geheel-getal (vuint). Net als bij UTF-8-codering slaat deze codering 7 bits per byte op, waarbij de 8e bit (MSB) gezet is als er een of meer bytes volgen. De bits van het niet-ondertekende gehele getal worden in de vuint in LSB-vorm opgeslagen.
Het hoogste niveau van een .mpy-bestand bestaat uit drie delen:
De header.
De globale qstr- en constanten-tabellen.
De raw-code voor de buitenste scope van de module. Deze buitenste scope wordt uitgevoerd wanneer het .mpy-bestand wordt geïmporteerd.
U kunt de inhoud van een .mpy-bestand inspecteren met behulp van mpy-tool.py, bijvoorbeeld (uitgevoerd vanuit de root van de hoofd-MicroPython-repository):
$ ./tools/mpy-tool.py -xd myfile.mpy
De header¶
De .mpy-header is:
grootte |
veld |
|---|---|
byte |
waarde 0x4d (ASCII ‘M’) |
byte |
.mpy-hoofdversienummer |
byte |
feature flags, native arch, secundair versienummer (was feature flags in oudere versies) |
byte |
aantal bits in een small int |
De derde byte is als volgt opgesplitst (MSB eerst):
bit |
betekenis |
|---|---|
7 |
gereserveerd, moet 0 zijn |
6 |
een architectuurspecifieke vlaggen-vuint volgt op de header |
5..2 |
native arch-nummer |
1..0 |
secundair versienummer |
Architectuurspecifieke vlaggen¶
Als bit #6 van de feature-flags-byte van de header gezet is, dan volgt op de header een vuint die optionele architectuurspecifieke informatie bevat. De inhoud van dit gehele getal hangt af van voor welke native architectuur het bestand bedoeld is.
Dit wordt momenteel gebruikt om op te slaan welke RISC-V-processoruitbreidingen het MPY-bestand nodig heeft om correct te werken, naast I, M, C en Zicsr. Verschillende varianten van ArmV7 worden geïdentificeerd door hun native-architectuurnummer, maar het hergebruiken van dat mechanisme zou de zaken voor RV32 en RV64 ingewikkelder maken.
MPY-bestanden gericht op RV32 of RV64 die geen bepaalde processoruitbreidingen nodig hebben, hoeven geen vlaggen-geheel-getal te verstrekken (naast het instellen van de juiste bit in de header). Het ontbreken van een vlaggenwaarde voor RV32- en RV64-MPY-bestanden wordt gebruikt om aan te geven dat er geen specifieke uitbreidingen nodig zijn, en bespaart één byte in de uiteindelijke uitvoer-binary.
Zie ook de opdrachtregeloptie -march-flags in zowel mpy-tool.py als mpy-cross, en de opdrachtregeloptie --arch-flags in mpy_ld.py om deze waarde in te stellen bij het maken van MPY-bestanden.
De globale qstr- en constanten-tabellen¶
Een .mpy-bestand bevat één enkele qstr-tabel en één enkele constanten-object-tabel. Deze zijn globaal voor het .mpy-bestand en worden gerefereerd door alle geneste raw-code-objecten. De qstr-tabel koppelt het interne qstr-nummer (intern aan het .mpy-bestand) aan het opgeloste qstr-nummer van de runtime waarin het .mpy-bestand wordt geïmporteerd. Dit verbindt het .mpy-bestand met de rest van het systeem waarbinnen het wordt uitgevoerd. De constanten-object-tabel wordt gevuld met referenties naar alle constante objecten die het .mpy-bestand nodig heeft.
grootte |
veld |
|---|---|
vuint |
aantal qstrs |
vuint |
aantal constante objecten |
… |
qstr-data |
… |
gecodeerde constante objecten |
Raw-code-elementen¶
Een raw-code-element bevat code, ofwel bytecode ofwel native machinecode. De inhoud is:
grootte |
veld |
|---|---|
vuint |
type, grootte en of er sub-raw-code-elementen zijn |
… |
code (bytecode of machinecode) |
vuint |
aantal sub-raw-code-elementen (alleen indien niet nul) |
… |
sub-raw-code-elementen |
De eerste vuint in een raw-code-element codeert het type code dat in dit element is opgeslagen (de twee minst-significante bits), of deze raw-code onderliggende objecten heeft (de derde minst-significante bit), en de lengte van de code die volgt (de hoeveelheid RAM die ervoor moet worden gereserveerd).
Na de vuint komt de code zelf. Tenzij het codetype viper-code met relocaties is, is deze code constante data en hoeft niet te worden gewijzigd.
Als deze raw-code onderliggende objecten heeft (zoals aangegeven door een bit in de eerste vuint), komt na de code een vuint die het aantal sub-raw-code-elementen telt.
Tot slot worden eventuele sub-raw-code-elementen recursief opgeslagen.