MicroPython .mpy-filer¶
MicroPython definierar konceptet med en .mpy-fil, vilket är ett binärt containerfilformat som innehåller förkompilerad kod och som kan importeras precis som en vanlig .py-modul. Filen foo.mpy kan importeras via import foo, så länge foo.mpy kan hittas på vanligt sätt av importmekanismen. Vanligtvis genomsöks varje katalog som listas i sys.path i tur och ordning. När en viss katalog genomsöks letas det först efter foo.py, och om den inte hittas letas det efter foo.mpy, varefter sökningen fortsätter i nästa katalog om ingen av dem hittas. På så sätt har foo.py företräde framför foo.mpy.
Dessa .mpy-filer kan innehålla bytecode som vanligtvis genereras från Python-källfiler (.py-filer) via programmet mpy-cross. För vissa arkitekturer kan en .mpy-fil även innehålla nativ maskinkod, som kan genereras på en mängd olika sätt, framför allt från C-källkod.
Kompilatorn mpy-cross¶
mpy-cross är korskompilatorn som omvandlar en .py-källfil till en .mpy-binärcontainer redo att importeras på kameran. Den ingår i MicroPythons källträd (samma som används för att bygga kamerans fasta programvara) och publiceras även som ett pip-paket för användning på värdsidan utan en fullständig firmware-utcheckning:
$ pip install --user mpy-cross
Eller via pipx:
$ pipx install mpy-cross
När den väl är installerad anropar du den på en enda källfil:
$ mpy-cross foo.py
Detta producerar foo.mpy i den aktuella katalogen, redo att kopieras till kamerans filsystem tillsammans med andra moduler eller att matas in i en ROMFS-avbild.
De mest användbara kommandoradsalternativen:
-o <path>– utdatasökväg för den genererade.mpy(standard är indatafilnamnet med utbytt filändelse;-o -skriver till stdout).-O<n>– optimeringsnivå0till3. Standardvärdet0bevarar assertions och fullständiga källkodspositioner;3tar bort assertions och docstrings och skriver omif __debug__-block. Nivån styr sammamicropython.opt_level-yta som körtiden exponerar.-march=<arch>– mål-native-arkitektur för funktioner dekorerade med@nativeoch@viper. Krävs när källkoden använder dessa dekoratorer. Värdet måste matcha kamerans MCU-klass: välj det från listan sommpy-cross --helpskriver ut, eller läs av det från kameran vid körning medsys.implementation._mpy.-s <path>– källsökvägssträng inbäddad i felsökningsinformationen för.mpy. Användbart när sökvägen på disk skiljer sig från den importsökväg som filen ska visas under i spårningar.-X emit=bytecode|native|viper– välj standardemitter för hela modulen (ett alternativ per modul till dekoratorerna@native/@viper).--version– skriver ut den.mpy-formatversion som denna binär emitterar. Det numret måste matcha den version som kamerans körtid stöder (se utgåvetabellen nedan), annars utlöser importenValueError('incompatible .mpy file').
Kör mpy-cross --help för den fullständiga flagglistan.
Pip-paketet exponerar också ett litet Python-modul-API så att byggskript kan styra kompilatorn i samma process i stället för att manuellt grena av en delprocess:
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 och mpy_cross.mpy_version är de tre ingångspunkterna; mpy_cross.CrossCompileError bär kompilatorns stderr när något går fel. Arkitekturkonstanterna (NATIVE_ARCH_ARMV7EMSP, NATIVE_ARCH_ARMV7EMDP osv.) matchar de strängar som flaggan -march accepterar.
Versionshantering och kompatibilitet för .mpy-filer¶
En given .mpy-fil kan vara kompatibel eller inte med ett givet MicroPython-system. Kompatibilitet baseras på följande:
Version av .mpy-filen: filens version måste matcha den version som stöds av systemet som laddar den.
Underversion av .mpy-filen: om .mpy-filen innehåller nativ maskinkod måste filens underversion matcha den version som stöds av systemet som laddar den. I annat fall, om det inte finns någon nativ maskinkod i .mpy-filen, ignoreras underversionen vid laddning.
Bitar för små heltal: .mpy-filen kräver ett minsta antal bitar i ett small integer och systemet som laddar den måste stödja minst så många bitar.
Nativ arkitektur: om .mpy-filen innehåller nativ maskinkod anger den arkitekturen för den maskinkoden och systemet som laddar den måste stödja exekvering av den arkitekturens kod.
Om ett MicroPython-system stöder import av .mpy-filer kommer fältet sys.implementation._mpy att finnas och returnera ett heltal som kodar versionen (de lägre 8 bitarna), funktioner och nativ arkitektur.
Försök att importera en .mpy-fil som misslyckas med ett av de fyra första testerna utlöser ValueError('incompatible .mpy file'). Försök att importera en .mpy-fil som misslyckas med testet för nativ arkitektur (om den innehåller nativ maskinkod) utlöser ValueError('incompatible .mpy arch').
Om importen av en .mpy-fil misslyckas, prova följande:
Bestäm den .mpy-version och de flaggor som stöds av ditt MicroPython-system genom att köra:
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()
Kontrollera giltigheten hos .mpy-filen genom att inspektera filens första två byte. Den första byten ska vara ett stort ’M’ och den andra byten är versionsnumret, vilket bör matcha systemversionen från ovan. Om det inte matchar, bygg om .mpy-filen.
Kontrollera om systemets .mpy-version matchar den version som emitterats av den
mpy-crosssom användes för att bygga .mpy-filen, vilken hittas medmpy-cross --version. Om det inte matchar, kompilera ommpy-crossfrån Git-arkivet utcheckat vid den tagg (eller hash) som rapporteras avmpy-cross --version.Se till att du använder rätt
mpy-cross-flaggor, som hittas med koden ovan, eller genom att inspektera Makefile-variabelnMPY_CROSS_FLAGSför den port du använder.Om den tredje byten i .mpy-filen har bit nr 6 satt, kontrollera då om de kodade arkitekturspecifika flaggbitarna (vuint) är kompatibla med målet du importerar filen på.
Följande tabell visar sambandet mellan MicroPython-utgåva och .mpy-version.
MicroPython-utgåva |
.mpy-version |
|---|---|
v1.23.0 och uppåt |
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 |
För fullständighetens skull visar nästa tabell den Git-commit i MicroPythons huvudarkiv där .mpy-versionen ändrades.
.mpy-versionsändring |
Git-commit |
|---|---|
6.2 till 6.3 |
bdbc869f9ea200c0d28b2bc7bfb60acd9d884e1b |
6.1 till 6.2 |
6967ff3c581a66f73e9f3d78975f47528db39980 |
6 till 6.1 |
d94141e1473aebae0d3c63aeaa8397651ad6fa01 |
5 till 6 |
f2040bfc7ee033e48acef9f289790f3b4e6b74e5 |
4 till 5 |
5716c5cf65e9b2cb46c2906f40302401bdd27517 |
3 till 4 |
9a5f92ea72754c01cc03e5efcdfe94021120531e |
2 till 3 |
ff93fd4f50321c6190e1659b19e64fef3045a484 |
1 till 2 |
dd11af209d226b7d18d5148b239662e30ed60bad |
0 till 1 |
6a11048af1d01c78bdacddadd1b72dc7ba7c6478 |
ursprunglig version 0 |
d8c834c95d506db979ec871417de90b7951edc30 |
Binär kodning av .mpy-filer¶
MicroPython .mpy-filer är ett binärt containerformat med kodobjekt (bytecode och nativ maskinkod) lagrade internt i en nästlad hierarki. Koden för den yttre modulen lagras först, och därefter följer dess barn. Varje barn kan ha ytterligare barn, till exempel i fallet med en klass som har metoder, eller en funktion som definierar en lambda eller comprehension. För att hålla filerna små samtidigt som ett stort intervall av möjliga värden tillhandahålls används konceptet med ett variabelt kodat osignerat heltal (vuint) på många ställen. Liknande UTF-8-kodning lagrar denna kodning 7 bitar per byte med den 8:e biten (MSB) satt om en eller flera byte följer. Det osignerade heltalets bitar lagras i vuinten i LSB-form.
Den översta nivån i en .mpy-fil består av tre delar:
Huvudet.
De globala qstr- och konstanttabellerna.
Råkoden för modulens yttre scope. Detta yttre scope exekveras när .mpy-filen importeras.
Du kan inspektera innehållet i en .mpy-fil med hjälp av mpy-tool.py, till exempel (kör från roten av MicroPythons huvudarkiv):
$ ./tools/mpy-tool.py -xd myfile.mpy
Huvudet¶
.mpy-huvudet är:
storlek |
fält |
|---|---|
byte |
värde 0x4d (ASCII ’M’) |
byte |
.mpy huvudversionsnummer |
byte |
funktionsflaggor, nativ arkitektur, mindre versionsnummer (var funktionsflaggor i äldre versioner) |
byte |
antal bitar i ett litet heltal |
Den tredje byten delas upp enligt följande (MSB först):
bit |
betydelse |
|---|---|
7 |
reserverad, måste vara 0 |
6 |
en arkitekturspecifik flagg-vuint följer efter huvudet |
5..2 |
nativt arkitekturnummer |
1..0 |
mindre versionsnummer |
Arkitekturspecifika flaggor¶
Om bit nr 6 i huvudets funktionsflaggbyte är satt följer en vuint som innehåller valfri arkitekturspecifik information efter huvudet. Innehållet i detta heltal beror på vilken nativ arkitektur filen är avsedd för.
Detta används för närvarande för att lagra vilka RISC-V-processortillägg MPY-filen behöver för att fungera korrekt utöver I, M, C och Zicsr. Olika varianter av ArmV7 identifieras med sitt nativa arkitekturnummer, men att återanvända den mekanismen skulle komplicera saker för RV32 och RV64.
MPY-filer som riktar sig mot RV32 eller RV64 och som inte behöver några särskilda processortillägg behöver inte tillhandahålla något flagg-heltal (utöver att sätta lämplig bit i huvudet). Avsaknaden av ett flaggvärde för RV32- och RV64-MPY-filer används för att indikera att inga specifika tillägg behövs, och sparar en byte i den slutliga utdatabinären.
Se även kommandoradsalternativet -march-flags i både mpy-tool.py och mpy-cross, samt kommandoradsalternativet --arch-flags i mpy_ld.py för att ställa in detta värde när MPY-filer skapas.
De globala qstr- och konstanttabellerna¶
En .mpy-fil innehåller en enda qstr-tabell och en enda konstantobjekttabell. Dessa är globala för .mpy-filen och refereras av alla nästlade råkodsobjekt. qstr-tabellen mappar internt qstr-nummer (internt i .mpy-filen) till det upplösta qstr-numret i körtiden som .mpy-filen importeras in i. Detta länkar .mpy-filen med resten av systemet den exekverar inom. Konstantobjekttabellen fylls med referenser till alla konstantobjekt som .mpy-filen behöver.
storlek |
fält |
|---|---|
vuint |
antal qstr:er |
vuint |
antal konstantobjekt |
… |
qstr-data |
… |
kodade konstantobjekt |
Råkodselement¶
Ett råkodselement innehåller kod, antingen bytecode eller nativ maskinkod. Dess innehåll är:
storlek |
fält |
|---|---|
vuint |
typ, storlek och huruvida det finns underordnade råkodselement |
… |
kod (bytecode eller maskinkod) |
vuint |
antal underordnade råkodselement (endast om icke-noll) |
… |
underordnade råkodselement |
Den första vuinten i ett råkodselement kodar typen av kod som lagras i detta element (de två minst signifikanta bitarna), huruvida denna råkod har några barn (den tredje minst signifikanta biten), och längden på koden som följer (mängden RAM som ska allokeras för den).
Efter vuinten kommer själva koden. Om inte kodtypen är viper-kod med relokeringar är denna kod konstant data och behöver inte modifieras.
Om denna råkod har några barn (vilket indikeras av en bit i den första vuinten) kommer det efter koden en vuint som räknar antalet underordnade råkodselement.
Slutligen lagras eventuella underordnade råkodselement, rekursivt.