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にクロージャを代入することで解決している。