ネイティブマシンコードの .mpy ファイルへの埋込み¶
この章では、Python 以外の言語で作成したネイティブマシンコードを埋め込んだ .mpy ファイルをビルドして操作する方法について説明します。これにより、C などの言語でコードを記述し、コンパイルして.mpyファイルにリンクし、このファイルを通常の Python モジュールのようにインポートできます。これにより、パフォーマンスが重要な機能を実装したり、別の言語で記述された既存のライブラリを使えるようになります。
ネイティブ .mpy ファイルを使用する主な利点の1つは、メインの MicroPython ファームウェアを再構築する必要なく、ネイティブマシンコードをスクリプトによって動的にインポートできることです。 MicroPython 外部 C モジュール でも C 言語でカスタムモジュールを定義できますが、メインファームウェアのイメージにコンパイルする必要があります。
ここでは C 言語を使用してネイティブモジュールを構築する方法について説明しますが、原則的にはスタンドアロンマシンコードにコンパイルできる言語であればなんでも .mpy ファイルに埋め込めます。
ネイティブ .mpy モジュールは、プロジェクトの tools/
ディレクトリにある mpy_ld.py ツール を使ってビルドできます。このツールは、オブジェクトファイル(.oファイル)の1組を取得・リンクして、ネイティブ .mpy ファイルを作成します。ツールの実行には CPython 3 とライブラリ pyelftools の v0.25 以上が必要です。
サポートされている機能と制限¶
.mpy ファイルには、MicroPython バイトコードやネイティブマシンコードを含められます。ネイティブマシンコードが含まれている場合、 .mpy ファイルには特定のアーキテクチャが関連付けられています。現在サポートされているアーキテクチャは次のとおりです(これらは 後述する ARCH 変数の有効なオプションです)。
x86
(32 ビット)x64
(64 ビット x86)armv6m
(ARM Thumb, Cortex-M0 など)armv7m
(ARM Thumb 2, Cortex-M3 など)armv7emsp
(ARM Thumb 2, 単精度浮動小数点, Cortex-M4F, Cortex-M7 など)armv7emdp
(ARM Thumb 2, 倍精度浮動小数点, Cortex-M7 など)xtensa
(ウィンドウなし, ESP8266 など)xtensawin
(ウィンドウサイズ 8, ESP32 など)
ネイティブ .mpy ファイルをコンパイル・リンクする場合、アーキテクチャの選択が必要です。該当ファイルはそのアーキテクチャにのみインポートできます。 .mpy ファイルの詳細については MicroPython .mpy ファイル ファイルを参照してください。
ネイティブコードは位置非依存コード(PIC)としてコンパイルし、グローバルオフセットテーブル(GOT)を使う必要がありますが、この詳細はアーキテクチャによって異なります。ネイティブコードで .mpy ファイルをインポートする場合、インポート機構はネイティブコードの基本的な再配置を行えます。これには、text, rodata, BSS セクションの再配置も含まれます。
リンカーとダイナミックローダーがサポートされている機能は次のとおりです:
- 実行可能コード (text)
- 文字列および定数データ(配列、構造体など)を含む読み取り専用データ (rodata)
- ゼロに初期化されるデータ (BSS)
- text 中の text, rodata, BSS へのポインター
- rodata 中の text, rodata, BSS へのポインター
既知の制限は次のとおりです。
- data セクションはサポートしていません。回避策: BSSデータを使い、データ値を明示的に初期化してください
- 静的 BSS 変数はサポートしていません。回避策: グローバル BSS 変数を使ってください
このため、C コードに書き込み可能なデータがある場合、データが初期化子なしでグローバルに定義され、関数内でのみ書き込まれていることを確認してください。
リンカーの制限: ネイティブモジュールは、MicroPython ファームウェア全体のシンボルテーブルに対してリンクされていません。むしろ mp_fun_table
(py/nativeglue.h
内)にある明示的にエクスポートされたシンボルテーブルに対してリンクされており、これはファームウェアのビルド時に決まってしまいます。したがって、たとえば任意の HAL/OS/RTOS/システムの関数を単純に呼び出すことはできません。
新しいシンボルをテーブルの最後に追加して、ファームウェアを再ビルドできます。シンボルは、同じ場所にある tools/mpy_ld.py
の fun_table
辞書にも追加する必要があります。これにより mpy がインポートされたときに mpy_ld.py
が新しいシンボルを見つけ出して、再配置できるようになります。シンボルが関数の場合には、マクロまたはスタブを py/dynruntime.h
に追加して、関数を簡単に呼び出せるようにする必要があります。
ネイティブモジュールの定義¶
ネイティブ .mpy モジュールは、.mpy のビルドに使う1組のファイルで定義します。ファイルシステムのレイアウトは、ソースファイルと Makefile の2つの主要部分で構成します。
最も単純なケースでは、単一の C ソースファイルのみが必要です。これには、.mpy モジュールにコンパイルされるすべてのコードを含みます。この C ソースコードには MicroPython 動的 API にアクセスするための py/dynruntime.h ファイルをインクルードし、少なくとも
mpy_init
という関数を定義する必要があります。この関数は、モジュールのエントリポイントであり、モジュールがインポートされるときに呼び出されます。必要に応じて、モジュールを複数の C ソースファイルに分割できます。モジュールの一部は Python でも実装できます。すべてのソースファイルは Makefile 中の SRC 変数に指定する必要があります(後述)。これには C ソースファイルと、結果の .mpy ファイルに含まれる Python ファイルの両方を含めます。
Makefile
はモジュールのビルド設定および .mpy モジュールをビルドするのに使うソースファイルの指定を含めます。変数MPY_DIR
には MicroPython リポジト(ヘッダファイル、関連する Makefile 断片、mpy_ld.py
ツールが存在)の場所を指定します。変数MOD
にはモジュールの名前を指定します。変数SRC
にはソースファイルの一覧を指定します。オプションでマシンアーキテクチャを指定するには変数ARCH
を使います。さらにpy/dynruntime.mk
のインクルードも必要です
最小限の例¶
この章では factorial
という簡単なモジュールの完全に機能する例を示します。このモジュールは入力の階乗を計算して結果を返す単一の関数 factorial.factorial(x)
を提供します。
ディレクトリのレイアウト
factorial/
├── factorial.c
└── Makefile
ファイル factorial.c
の内容は以下のとおりです:
// MicroPython API にアクセスするためのヘッダファイルをインクルード
#include "py/dynruntime.h"
// 階乗を計算するヘルパー関数
STATIC mp_int_t factorial_helper(mp_int_t x) {
if (x == 0) {
return 1;
}
return x * factorial_helper(x - 1);
}
// Python から factorial(x) として呼び出される関数
STATIC mp_obj_t factorial(mp_obj_t x_obj) {
// MicroPython 入力オブジェクトから整数を抽出
mp_int_t x = mp_obj_get_int(x_obj);
// 階乗を計算
mp_int_t result = factorial_helper(x);
// 計算結果を MicroPython 整数オブジェクトに変換して戻す
return mp_obj_new_int(result);
}
// 上の関数の Python 参照を定義
STATIC MP_DEFINE_CONST_FUN_OBJ_1(factorial_obj, factorial);
// これはエントリポイントであり、モジュールをインポートしたときに呼び出される
mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) {
// これが最初になければならず、グローバル辞書などをセットアップする
MP_DYNRUNTIME_INIT_ENTRY
// モジュールの名前空間で関数を利用できるようにする
mp_store_global(MP_QSTR_factorial, MP_OBJ_FROM_PTR(&factorial_obj));
// これが最後になければならず、グローバル辞書をリストアする
MP_DYNRUNTIME_INIT_EXIT
}
ファイル Makefile
の内容は以下のとおりです:
# Location of top-level MicroPython directory
MPY_DIR = ../../..
# Name of module
MOD = factorial
# Source files (.c or .py)
SRC = factorial.c
# Architecture to build for (x86, x64, armv6m, armv7m, xtensa, xtensawin)
ARCH = x64
# Include to get the rules for compiling and linking the module
include $(MPY_DIR)/py/dynruntime.mk
モジュールのコンパイル¶
ネイティブ .mpy ファイルをビルドするために必要となるツールは以下の通りです:
- MicroPython リポジトリ(少なくとも
py/
とtools/
ディレクトリ)。 - CPython 3 とライブラリ pyelftools (
pip install 'pyelftools>=0.25'
などでインストール)。 - GNU make.
- ターゲットアーキテクチャ用の C コンパイラ(C ソースが使われている場合)。
- オプションで MicroPython リポジトリでビルドした
mpy-cross
(.py ソースを使用している場合)。
実行するターゲットに対して適切な ARCH
を選択してください。ビルドは次のように行います:
$ make
Makefile を変更しなくても、次のようにしてターゲットのアーキテクチャを指定できます:
$ make ARCH=armv7m
MicroPython でのモジュールの使い方¶
モジュールをビルドすると factorial.mpy
というファイルが作成されます。これをコピーして MicroPython システムのファイルシステムでアクセスできるようにし、インポートパスで見つけられるようにします。これで作成したモジュールを、他のモジュールと同じように Python でアクセスできるようになります。例えば次のように使います:
import factorial
print(factorial.factorial(10))
# 3628800 が表示されるはず
さらなる例¶
ネイティブ .mpy モジュールで利用可能な機能の多くを示すさらなる例については examples/natmod/
を参照してください。以下の例があります:
- 複数の C ソースファイルの利用
- C コード と Python コードを一緒に含める
- rodata と BSS データ
- メモリー割り当て
- 浮動小数点の利用
- 例外処理
- 外部 C ライブラリの取り込み