MicroPython .mpy ファイル
MicroPython は .mpy ファイルの概念を定義しています。これはプリコンパイルされたコードを保持するバイナリコンテナファイル形式であり、通常の .py モジュールのようにインポートできます。ファイル foo.mpy は、foo.mpy をインポート機構が通常の方法で見つけることができる限り import foo でインポートできます。通常 sys.path にリストされている各ディレクトリが順番に検索されます。特定のディレクトリを検索で foo.py が最初に検索され、それが見つからなければ foo.mpy が検索されます。どちらも見つからない場合は次のディレクトリで検索が続行されます。そのため foo.py は foo.mpy に優先します。
これらの .mpy ファイルには、通常 mpy-cross プログラムで Python ソースファイル(.py ファイル)から生成されるバイトコードを含めることができます。一部のアーキテクチャでは .mpy ファイルにネイティブマシンコードを含めることもできます。ネイティブコードはマシンコードはさまざまな方法で生成できますが、C 言語ソースコードから生成することが特に多いです。
.mpy ファイルのバージョンと互換性
利用しようとする .mpy ファイルは、特定の MicroPython システムと互換性がある場合と互換性がない場合があります。互換性は以下に基づいています:
.mpy ファイルのバージョン: ファイルのバージョンは、それをロードするシステムでサポートされているバージョンと一致する必要があります。
.mpy ファイルのサブバージョン: .mpy ファイルがネイティブのマシンコードを含んでいる場合、ファイルのサブバージョンは、それを読み込むシステムがサポートするバージョンと一致しなければなりません。そうでない場合、.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', '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()
ファイルの先頭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_FLAGSMakefile 変数を調べてください。.mpy ファイルの3番目のバイトに第6ビットが立っている場合、エンコードされたアーキテクチャ固有のフラグビット vuint が、ファイルをインポートするターゲットと互換性があるかどうかを確認します。
次の表は、MicroPythonリリースと.mpyバージョンの対応を示しています。
MicroPython リリース |
.mpy バージョン |
|---|---|
v2.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 |
完全を期すため、次の表は .mpy バージョンが変更されたメイン MicroPython リポジトリの Git コミットを示しています。
.mpy バージョン変更 |
Git コミット |
|---|---|
6.2 から 6.3 |
bdbc869f9ea200c0d28b2bc7bfb60acd9d884e1b |
6.1 から 6.2 |
6967ff3c581a66f73e9f3d78975f47528db39980 |
6 から 6.1 |
d94141e1473aebae0d3c63aeaa8397651ad6fa01 |
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 メジャーバージョン番号 |
バイト |
機能フラグ、ネイティブアーキテクチャ、マイナーバージョン番号(以前のバージョンでは機能フラグ) |
バイト |
スモール整数のビット数 |
3番目のバイトは次のように分割されます(MSBが最初)。
ビット |
意味 |
|---|---|
7 |
予約済み。0である必要あり |
6 |
アーキテクチャ固有フラグ(vuintがヘッダーに続くことを示す) |
5..2 |
ネイティブアーキテクチャ番号 |
1..0 |
マイナーバージョン番号 |
アーキテクチャ固有フラグ
ヘッダーにある機能フラグバイトのビット6が設定されている場合、オプションのアーキテクチャ固有の情報を含む vuint がヘッダーに続きます。この整数の内容は、ファイルがどのネイティブアーキテクチャ向けであるかによって異なります。
これは現在、I、M、C、Zicsr に加えて、MPY ファイルが正しく動作するために必要なRISC-Vプロセッサ拡張機能を格納するために使われています。ArmV7 の各種フレーバーはネイティブアーキテクチャ番号で識別されますが、このメカニズムを再利用すると RV32 および RV64 では処理が複雑になります。
RV32 または RV64 をターゲットとするMPYファイルで、特定のプロセッサ拡張を必要としない場合は、フラグ整数(およびヘッダー内の適切なビットの設定)を指定する必要はありません。RV32 および RV64 MPY ファイルにフラグ値が存在しないのは、特定の拡張が必要ないことを示すためであり、最終的な出力バイナリで1バイトを節約します。
MPY ファイルを作成する際にこの値を設定するには mpy-tool.py と mpy-cross の両方にある -march-flags コマンドラインオプションと mpy_ld.py の --arch-flags コマンドラインオプションも参照してください。
グローバルな 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 コード要素が再帰的に保存されます。