Perlで実装されたPerl5の仮想マシンです。Compilerと呼ばれています。APVMはこのBモジュールを利用して構文木を解釈・実行するモジュールです。この文書では,Perl5の仮想マシンについて概説しつつ,APVMとPerl5実装との対応について解説します。MachinePerl5の仮想マシンはスタックマシンであり,組み込み演算子やサブルーチンなどの手続きの引数と戻り値を,スタックを通じてやり取りします。この仮想マシンのマシンコードはopcodeと呼ばれ,これがコンパイルされたPerlプログラムの最小単位となります。code)と呼ばれる関数として実現されます。opcodeオブジェクトは他のopcodeオブジェクトへのリンクを持つ木構造を成しており,このopcodeの木を構文木と呼びます。したがって,Perlプログラムを実行するというのは,opcodeを実行しつつ,この木構造をたどって行く過程ということになります。&#23450;&#25968;[20]構文木は子ノードから実行されるので,この構文木を解釈すると以下の順になります。printそれぞれのopcodeは必要に応じてスタックから引数をポップし,戻り値をプッシュします。opcodeの実行とスタックの中身を同時に表すと以下のようになります。mark+1&#12363;&#12425;TOP&#12414;&#12391;&#12434;&#21360;&#23383;&#12375;&#65292;&#32080;&#26524;(&#30495;)&#12434;PUSHこれが構文木を解釈する基本的な流れです。プログラムの分岐やサブルーチンの呼び出しなどがあると更に複雑になりますが,一連の流れは同じです。以下のセクションでは,仮想マシンの実装の中で特に重要なコンポーネントについて説明します。(PL_stack_base)Perlプログラムの手続きと戻り値のために使われるスタックです。このスタックは組み込み関数とサブルーチンの両方で使われます。現在のperl5の実装では,このスタックはCの配列で表現され,必要に応じてrealloc()で拡張されます。スタックの先頭はスタックポインタ(PL_stack_sp)として参照できるのですが,ppcode内ではこのグローバルなスタックポインタを一旦ローカルにコピーします(dSP)。PUSHs/POPs/TOPsなどのマクロはこのローカルコピーを参照します。スタックポインタを使った操作が終わったところでPUTBACKマクロによりローカルスタックポインタをグローバルスタックポインタ変数に戻します。API(call_sv()など)を呼び出した後に使用します。APVMではこれはPerlの配列で表現され,スタックポインタは配列の最後の添え字です。ローカルコピーは作りません。(PL_markstack)可変長引数を扱うためのスタックのマーカーです。二項演算子などは引数の数が固定ですが,printのように引数の数が可変長である組み込み関数もあります。可変長引数を扱うためには,引数スタック中で引数が始まる位置を保存する必要があります。このマーカーは入れ子になる可能性があるので,このマーカーそれ自体もスタックに保存します。可変長引数の開始を宣言するためには,PUSHMARK(SP)マクロを使います。dMARKマクロによりスタックから値をポップし,MARKマクロを使えるようにします。FamilyPerlプログラムの最小単位である,手続きとデータを持ったオブジェクトです。opcodeクラス群はCの構造体の先頭メンバをいくつか共有する構造体群として表現されます。opcodeオブジェクトの持つ手続きは対応するppcodeであり,op_ppaddrメンバで参照します。データはPerlの値やCの値,または他のopcodeへのリンクです。各opcodeオブジェクトは名前と外部出力用の説明を持ち,それぞれOP_NAME(op),OP_DESC(op)マクロで得ることができます。PPcodesopcodeが持つ手続きで,実際に行う処理を実装した関数です。}マクロをいくつか展開すると以下のようになります。}一つひとつを順に追うと以下のようになります。dSPローカルスタックポインタ変数(SP)を宣言し,グローバルスタックポインタの値で初期化します。1)スタックポインタに値を1つプッシュすることを宣言します。このとき,必要に応じてスタックは拡張されます。cSVOPx_sv(PL_op)現在実行中のopcodeのsvフィールドを参照します。PUSH(sv)スタックポインタを通じて引数スタックに値を1つプッシュします。PUTBACKローカルスタックポインタをグローバルスタックポインタ変数に戻します。PL_op->op_next次に実行するopcodeを返します。OP_CONSTであれば可能性のあるプログラムの経路は常に一つですが,OP_COND_EXPRのような制御を担うopcodeであればPL_op->next以外のopcodeを返すことがあります。ppcodeはopcodeオブジェクトのop_ppaddrメンバを通じて取得・変更することができます。hackとして知られています。autoboxはこのPL_checkハックを用いてプリミティブ値に対するメソッド呼び出しを実現しています。(PL_runops)構文木を解釈・実行するループの実装です。デフォルトでは,run.cにあるPerl_runops_standard()が用いられます。}PL_opは現在実行中のopcodeオブジェクトが入っているグローバル変数(またはTLS変数)です。op_ppaddrはopcodeに対応したppcodeが入っており,ppcodeは次に実行するopcodeを返すことになっています。PERL_ASYNC_CHECK()は単にセーフシグナルの処理なので実行には関係ありません。したがって,インタプリタループの実体は一行しかありません。ところで,このようなopcodeの多態性を利用した実行ループと対極にあるのが,switch文やifの連鎖による分岐を利用した実行ループです。Perl4の実行ループはそのような実装でしたし,Perl5の実装の中にもswitchによる実行ループも存在します。scope.cにあるleave_scope()はまさに巨大なswitch文を利用した実行ループでスコープの後処理を行っています。インタプリタループについては,APVMのrunops_standard()でも実装はほぼ同じです。componentsこの他にもいくつか重要なコンポーネントがあります。(PL_curstackinfo)なお,この文書ではPerlの値の実装であるSV構造体群については解説しません。gv.[hc]を参照してください。Tracingperlを-DDEBUGGINGコンパイルオプションを指定してビルドすると,プログラムの実行をopcodeレベルでトレースできるようになります。perlコマンドに-Dtまたは-Dtsを渡して実行してみてください。APVMにもopcodeトレース機能があります。環境変数APVM_DEBUGにtraceを指定すると,opcodeトレースを行います。stackを指定すると,opcodeトレースと同時に引数スタックの中身も報告します。Perlの標準モジュールB::Conciseでも構文木を出力することができます。このとき-execオプションを渡すと,実行順にopcodeを並べて出力します。B::Conciseでは静的な解析しかできません。VMで使えるようにしたモジュールです。このモジュールは-DDEBUGGINGが指定されていないperlでも利用できます。テクニカルトーク#11で発表されました。