Opcodeのトレースを実装した

OpcodeのトレースにおいてAcme::Perl::VMで実装したOpcode traceを,普通のPerlでも実装した。これはperl(1)の-Dtsオプションを通常のPerlで実行できるようにし,さらに出力結果をより見やすくしたものと言える。

使用例:

$ perl -d:Optrace -e 'print "Hello, world!\n"'
Entering RUNOPS (-e:1)
 ()
 null SCALAR
 ()
 const("Hello, world!\n") SCALAR
 ("Hello, world!\n")
 stringify SCALAR KIDS
 ("Hello, world!\n")
Leaving RUNOPS (-e:1)
Entering RUNOPS (-e:0)
()
enter
 ()
 nextstate(main -e:1) VOID
 ()
 pushmark SCALAR
 ()
 const("Hello, world!\n") SCALAR
 ("Hello, world!\n")
 print VOID KIDS
Hello, world!
 (YES)
 leave VOID KIDS PARENS
()
Leaving RUNOPS (-e:0)

perl(1)の-dオプションで指定すればコンパイルのタイミングからすべてトレースする。また,useしたあとDevel::Optrace->set(-all => 1)などとして実行時にトレース設定することもできる。出力はデフォルト(-all)だとRUNOPSの出入り(-runops),Opcodeトレース(-trace),引数スタック(-stack)のすべてを指定したのと同じになる。インデントはブロックの深さを表す。
なお,上記の例だとRUNOPS*1が二回行われていて,二度目のRUNOPSがメインプログラムの実行となっている。一度目のRUNOPS実行はおそらく定数畳み込みと関係がある。上記の例だと効果は不明だが,以下の例が分かりやすい。

$ perl -d:Optrace -e 'warn 1+2'
Entering RUNOPS (-e:1)
 ()
 const(1) SCALAR
 (1)
 const(2) SCALAR
 (1,2)
 add SCALAR KIDS
 (3)
Leaving RUNOPS (-e:1)
Entering RUNOPS (-e:0)
()
enter
 ()
 nextstate(main -e:1) VOID
 ()
 pushmark SCALAR
 ()
 const(3) SCALAR
 (3)
 warn VOID KIDS
3 at -e line 1.
 (YES)
 leave VOID KIDS PARENS
()
Leaving RUNOPS (-e:0)

一度目のRUNOPSの実行はコンパイルフェーズに行われ,定数同士の加算を行う。二度目のRUNOPSの実行はメインプログラムの実行であり,引数スタックにプッシュするのは1+2の演算結果である3となっている。

*1:構文木を実行するインタプリタループで,このRUNOPSはユーザーレベルで書き換えることができる。run.cにあるPerl_runops_standard()とdump.cにあるPerl_runops_debug()が組み込みのrunopsである。Devel::OptraceはRUNOPSを書き換えることによってその機能を提供している。