Visit opcode tree with B

Test::Vars の解説を少しだけ。

Perl構文木を歩き回るには、B::walkoptree()かB::walkoptree_exec()を使う。

基本的なやり方は以下の通りである。

#!perl -w
use strict;
use feature 'say';

use B;

my $cv = B::svref_2object(sub{ $_[0] + 42 });

local *B::OP::visit;
*B::OP::visit = sub {
    my($op) = @_;

    say $op->name;
};

say "<walkoptree>";
B::walkoptree($cv->ROOT, 'visit');

say "<walkoptree_exec>";
B::walkoptree_exec($cv->START, 'visit');
__END__

B::svref_2object()にリファレンスを与えると、対応するオブジェクトが返る。この場合はCV*だ。Pure Perlのサブルーチンは内部に構文木を持っており、これはROOT*1とSTART*2でアクセスできる。
ROOTというのは構文木のROOTであり、STARTは構文木の中で実行を開始するノードである。
上記のスクリプトに対応するものをB::Conciseで見てみる。

$ perl -MO=Concise -e '$_[0] + 42'
6  <@> leave[1 ref] vKP/REFC ->(end)
1     <0> enter ->2
2     <;> nextstate(main 1 -e:1) v:{ ->3
5     <2> add[t2] vK/2 ->6
-        <1> ex-aelem sK/2 ->4
-           <1> ex-rv2av sKR/1 ->-
3              <#> aelemfast[*_] s ->4
-           <0> ex-const s ->-
4        <$> const[IV 42] s ->5

この構文木では、ROOTは最初の6 leaveであり、STARTは 1 enter である*3

ここで、最初のスクリプトを実行してみると、以下のような結果となるはずだ*4

<walkoptree>
leavesub
lineseq
nextstate
add
null
null
aelemfast
null
const
<walkoptree_exec>
nextstate
aelemfast
const
add
leavesub

walkoptree()は深さ優先で、walkoptree_exec()は実行順にvisitする。あとは、目的に応じて必要なopcodeを探し、操作すればよい。

なお、walkoptree()の第二引数はメソッド名でなければならない。しかし実際には、クロージャとして与えるほうが都合がいいこともある。そこで、localで*B::OP::visitを保護しておき、*B::OP::visitにクロージャを代入することで解決している。

*1:PerlAPIのCvROOTに対応

*2:PerlAPIのCvSTARTに対応

*3:最初の数字は実行順を示している。"-"は最適化によって取り除かれたノードで、実行されないことを示す。

*4:実際には、Perlのバージョンによって微妙に差がある