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å 0 till 3. Standardvärdet 0 bevarar assertions och fullständiga källkodspositioner; 3 tar bort assertions och docstrings och skriver om if __debug__-block. Nivån styr samma micropython.opt_level-yta som körtiden exponerar.

  • -march=<arch> – mål-native-arkitektur för funktioner dekorerade med @native och @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 som mpy-cross --help skriver ut, eller läs av det från kameran vid körning med sys.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 importen ValueError('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-cross som användes för att bygga .mpy-filen, vilken hittas med mpy-cross --version. Om det inte matchar, kompilera om mpy-cross från Git-arkivet utcheckat vid den tagg (eller hash) som rapporteras av mpy-cross --version.

  • Se till att du använder rätt mpy-cross-flaggor, som hittas med koden ovan, eller genom att inspektera Makefile-variabeln MPY_CROSS_FLAGS fö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.