MicroPython .mpy ファイル

MicroPython は .mpy ファイルの概念を定義しています。これはプリコンパイルされたコードを保持するバイナリコンテナファイル形式であり、通常の .py モジュールのようにインポートできます。ファイル foo.mpy は、foo.mpy をインポート機構が通常の方法で見つけることができる限り import foo でインポートできます。通常 sys.path にリストされている各ディレクトリが順番に検索されます。特定のディレクトリを検索で foo.py が最初に検索され、それが見つからなければ foo.mpy が検索されます。どちらも見つからない場合は次のディレクトリで検索が続行されます。そのため foo.pyfoo.mpy に優先します。

これらの .mpy ファイルには、通常 mpy-cross プログラムで Python ソースファイル(.py ファイル)から生成されるバイトコードを含めることができます。一部のアーキテクチャでは .mpy ファイルにネイティブマシンコードを含めることもできます。ネイティブコードはマシンコードはさまざまな方法で生成できますが、C 言語ソースコードから生成することが特に多いです。

.mpy ファイルのバージョンと互換性

利用しようとする .mpy ファイルは、特定の MicroPython システムと互換性がある場合と互換性がない場合があります。互換性は以下に基づいています:

  • .mpy ファイルのバージョン: ファイルのバージョンは、それをロードするシステムでサポートされているバージョンと一致する必要があります。
  • スモール整数ビット: .mpy ファイルにはスモール整数の最小ビット数の要求があり、.mpy をロードするシステムは少なくともこの数のビットをサポートする必要があります。
  • ネイティブアーキテクチャ: .mpyファイルにネイティブマシンコードが含まれている場合、そのマシンコードのアーキテクチャの指定があり、.mpy をロードするシステムはそのアーキテクチャのコードの実行をサポートする必要があります。

MicroPython システムが .mpy ファイルのインポートをサポートしている場合には、 sys.implementation._mpy フィールドが存在し、バージョン(下位8ビット)、機能、ネイティブアーキテクチャをエンコードした整数を返します。

最初の4つのテストの1つに失敗した .mpy ファイルをインポートしようとすると ValueError('incompatible .mpy file') が発生します。(ネイティブマシンコードが含まれている場合)ネイティブアーキテクチャのテストに失敗した .mpy ファイルをインポートしようとすると ValueError('incompatible .mpy arch') が発生します。

.mpy ファイルのインポートに失敗した場合は、次を試してください:

  • 以下を実行して、MicroPython システムでサポートされている .mpy バージョンとフラグを確認します:

    import sys
    sys_mpy = sys.implementation._mpy
    arch = [None, 'x86', 'x64',
        'armv6', 'armv6m', 'armv7m', 'armv7em', 'armv7emsp', 'armv7emdp',
        'xtensa', 'xtensawin'][sys_mpy >> 10]
    print('mpy version:', sys_mpy & 0xff)
    print('mpy flags:', end='')
    if arch:
        print(' -march=' + arch, end='')
    print()
    
  • ファイルの先頭2バイトを調べて、.mpy ファイルの妥当性を確認します。先頭バイトは大文字の 'M' で、2バイト目はバージョン番号です。このバージョン番号は、上記のシステムバージョンと一致する必要があります。一致しない場合は .mpy ファイルを再作成してください。

  • .mpy ファイルを作成するのに使った mpy-cross の出した .mpy バーションがシステムの .mpy バージョンと一致するかを確認します。 mpy-cross の出すバージョンは mpy-cross --version でわかります。一致しない場合は mpy-cross --version によって報告されたタグ(またはハッシュ)でチェックアウトした Git リポジトリにある mpy-cross で再コンパイルします。

  • mpy-cross フラグが正しいかを確認します。フラグは上記のコードで見つけるか、使っているポートの MPY_CROSS_FLAGS Makefile 変数を調べてください。

次の表は、MicroPythonリリースと.mpyバージョンの対応を示しています。

MicroPython リリース .mpy バージョン
v1.19 以上 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

完全を期すため、次の表は .mpy バージョンが変更されたメイン MicroPython リポジトリの Git コミットを示しています。

.mpy バージョン変更 Git コミット
5 から 6 f2040bfc7ee033e48acef9f289790f3b4e6b74e5
4 から 5 5716c5cf65e9b2cb46c2906f40302401bdd27517
3 から 4 9a5f92ea72754c01cc03e5efcdfe94021120531e
2 から 3 ff93fd4f50321c6190e1659b19e64fef3045a484
1 から 2 dd11af209d226b7d18d5148b239662e30ed60bad
0 から 1 6a11048af1d01c78bdacddadd1b72dc7ba7c6478
初期バージョン 0 d8c834c95d506db979ec871417de90b7951edc30

.mpy ファイルのバイナリエンコーディング

MicroPython .mpy ファイルは、入れ子になった階層内にコードオブジェク(バイトコードとネイティブマシンコード)を格納したバイナリコンテナー形式です。外側のモジュールのコードが最初に格納され、その後に子モジュールが続きます。それぞれの子モジュールは、(メソッドを持つクラスの場合に)さらに子を持つことや、ラムダや内包を定義する関数を持つことがあります。大きな範囲の値を提供しながらもファイルを小さく保つために、多くの箇所で可変エンコード符号なし整数(variably-encoded-unsigned-integer: vuint)の概念を使用します。utf-8 エンコーディングと同様に、このエンコーディングは1バイトあたり7ビットを格納し、1つ以上のバイトが続く場合は8ビット目(MSB)が設定されます。符号なし整数のビットは、LSB 形式で vuint に格納されます。

.mpyファイルのトップレベルは3つの部分で構成されています:

  • ヘッダー。
  • The global qstr and constant tables.
  • モジュールの外部スコープの raw コード。この外部スコープは .mpy ファイルがインポートされるときに実行されます。

.mpy ファイルの内容を調べるには、たとえば mpy-tool.py が使えます(MicroPython のメインリポジトリのルートから実行します):

$ ./tools/mpy-tool.py -xd myfile.mpy

ヘッダー

.mpy のヘッダーは次のとおりです:

サイズ フォールド
バイト 値 0x4d (ASCII の "M')
バイト .mpy バージョン番号
バイト 機能フラグ
バイト スモール整数のビット数

グローバルな qstr と定数のテーブル

.mpy ファイルには qstr テーブルと定数オブジェクトテーブルがあります。これらは .mpy ファイルに対してグローバルなもので、入れ子になったすべての raw コードオブジェクトから参照されます。qstr テーブルは(.mpy ファイル内の)内部 qstr 番号を .mpy ファイルがインポートされているランタイムの解決済 qstr 番号にマップします。これは .mpy ファイルをその中で実行されるシステムの残りの部分とリンクします。定数オブジェクトテーブルには .mpy ファイルが必要とするすべての定数オブジェクトへの参照が置かれます。

サイズ フォールド
vuint qstr 数
vuint 定数オブジェクト数
... qstr データ
... エンコードした定数オブジェクト

raw コードの要素

raw コードの要素には、バイトコードまたはネイティブマシンコードのコードが含まれます。その内容は次のとおりです:

サイズ フォールド
vuint タイプ、サイズ、サブ raw コードの有無
... コード(バイトコードまたはマシンコード)
vuint サブ raw コード要素数(0でない場合のみ)
... サブ raw コード要素

raw コード要素の最初の vuint は、この要素に格納されているコードの種別(最下位2ビット)、この raw コードに子があるかどうか(最下位3ビット)、それに続くコードの長さ(そのために確保すべきRAMの量)をエンコードしています。

vuint に続いてコード自体が続きます。コード種別が再配置を伴うバイパーコードでない限り、このコードは定数データであり、更新する必要はありません。

この raw コードが(最初の vuint のビットで示されるように)子を持つ場合、コードの後に、サブ raw コードの要素数をカウントする vuint が来ます。

最後にサブ raw コード要素が再帰的に保存されます。