MicroPython 文字列の隔離化¶
MicroPython は 文字列の隔離化 を使って RAM と ROM の両方に保存します。これにより、同じ文字列の重複コピーを保存する必要がなくなります。関数または変数名のようなものがコードの複数の場所に現れる可能性が非常に高いため、これは主にコードの識別子に適用されます。MicroPython では、インターンされた文字列は QSTR (uniQue STRing)と呼ばれます。
QSTR 値(qstr 型)は、QSTR プールのリンクリストへのインデックスです。QSTRは重複排除プロセス中の高速比較のために、長さとコンテンツのハッシュを保存します。文字列を操作するすべてのバイトコード操作は、QSTR 引数を使います。
コンパイル時の QSTR 生成¶
MicroPython の C ソースコードでは、ビルド後のファームウェアに隔離する必要のある文字列はすべて MP_QSTR_Foo として書いてあります。これはコンパイル時に、QSTR プール内の "Foo" を指すインデックスである qstr 値に評価されます。
Makefile 中の多段階の処理がこの機能を実現しています。この処理は大きく次の3つの段階があります:
- ソースコード中のすべての
MP_QSTR_Fooトークンを検出 - すべての文字列データ(長さとハッシュを含む)を持つ静的な QSTR プールを生成
MP_QSTR_Fooすべてを(プリプロセッサを使って)対応するインデックスに置換
MP_QSTR_Foo トークンは次の2つのソースコードから検索されます:
$(SRC_QSTR)で参照されるすべてのソースファイル。これは、すべてのCコード(すなわちpy,extmod,ports/stm32など)ですが、libのようなサードパーティのコードは含みません。$(QSTR_GLOBAL_DEPENDENCIES)で追加されるもの(mpconfig*.hを含む)。
注記: (mpy-tool.py によって生成される) frozen_mpy.c には独自の QSTR 生成とプールがあります。
MP_QSTR_Foo 構文を使って表現できない追加の文字列(たとえば、英数字以外の文字を含んだもの)は、 $(QSTR_DEFS) に指定する qstrdefs.h と qstrdefsport.h で明示的に与えます。
処理は次の段階を踏みます:
qstr.i.lastは、C プリプロセッサをとおした各入力ファイルを結合したものです。これは、条件付きで無効化されたコードが削除され、マクロが展開されていることを意味します。これは、ビルドしたファームウェアで使われていない文字列をプールに追加しないことを意味します。この段階では(QSTR_GEN_EXTRA_CFLAGSによって追加されたNO_QSTRマクロのおかげで)MP_QSTR_Fooのための定義がないため、何の作用もなくこの段階を通過します。このファイルには、行番号情報を含むプリプロセッサからのコメントも含まれます。この段階では、変更されたファイルのみを使うことに注意してください。つまりqstr.i.lastには直前のコンパイル以降に変更されたファイルのデータのみが含まれます。qstr.splitは qstr.i.last に対してmakeqstrdefs.py splitを実行して作成される空のファイルです。このファイルは、この段階が実行されたことを示すための依存関係としてのみ使われます。このスクリプトは、元の入力 C ソースファイルごとに1つのファイルを出力します。出力ファイルはgenhdr/qstr/...file.c.qstrのようなファイル名であり、ファイルの中身は抽出した QSTR のみとなります。各 QSTR はQ(Foo)の形式で出力されます。この段階は、qstr.i.lastにある増分更新から生成された新しいデータを既存ファイルにマージするために必要です。qstrdefs.collected.hは、makeqstrdefs.py catを使ってgenhdr/qstr/*を全て結合した出力です。これは、ソースコード内に見つかったMP_QSTR_Fooの完全なセットです。フォーマットはQ(Foo)のまま、1行に1つずつ、重複ありとなっています。このファイルは、qstr のセットが変更された場合にのみ更新されます。QSTR データのハッシュは別のファイル(qstrdefs.collected.h.hash)に書き込まれ、ビルド全体の変更を追跡できます。qstrdefs.preprocessed.hは qstrdefs* から QSTR を追加します。これはqstrdefs.collected.hをqstrdefs*.hと結合し、各行をQ(Foo)から"\Q(Foo)"に変換し、プリプロセッサを変更せずにとおしたものです。プリプロセッサを使って、qstrdefs*.hの条件付きコンパイルを処理します。その後、変換を元のQ(Foo)に戻し、qstrdefs.preprocessed.hとして保存します。qstrdefs.generated.hはmakeqstrdata.pyの出力です。qstrdefs.preprocessed.h(と、追加でハードコーディングされたもの)中のQ(Foo)それぞれについてQDEF(MP_QSTR_Foo, (const byte*)"hash" "Foo")を出力します。
次に、メインのコンパイルで、 qstrdefs.generated.h について次の2つのことが起きます:
qstr.hでは、各 QDEF は列挙型のエントリになり、ソースコード中でMP_QSTR_Fooを使えるようになります。これは QSTR テーブル内の文字列のインデックスと同等です。qstr.cでは、実際の QSTR データテーブルがmp_qstr_const_pool->qstrsの要素として生成されます。
実行時のQSTR生成¶
追加の QSTR プールを実行時に作成して、文字列を追加できます。たとえば、次のコードがあるものとします:
foo[x] = 3
x の値の QSTR を作成して、 "load attr" バイトコードで使えるようにする必要があります。
また、Python コードをコンパイルするときは、識別子とリテラルの QSTR を作成する必要があります。注記: 10文字より短いリテラルのみが QSTR になります。これは、ヒープ上の通常の文字列が常に最小16バイト(1 GCブロック)を占有するのに対し、QSTRを使った場合はより効率的にプールにパックできるためです。
QSTRプール(および文字列データを格納する基盤となる「チャンク」)は、最小サイズでオンデマンドでヒープに割り当てられます。