はじめに
Perlの備忘録として書いてます。
Perlは一般的にインタプリタ言語と呼ばれますが、内部では実行前にソースコードを「Opcode Tree(オペコードツリー)」と呼ばれる中間表現(構文木)にコンパイルしています。
今回は、このコンパイル済みの内部構造を調査・操作するための B モジュール群について。
ソースコードの復元:B::Deparse
B::Deparse を使って、コンパイルされたOptreeからPerlのソースコードを再生成(逆コンパイル)することができます。
これを利用すると、Perlのコンパイラがコードをどのように解釈・最適化したかを確認したり、難読化されたコードを解読したりすることが可能です。
実行には -MO=Deparse オプションを使用します。これはコンパイラ・バックエンドへのインターフェースである O モジュール経由で B::Deparse を呼び出す記述です。
$ perl -MO=Deparse -e 'print "Hello" if 1'
print 'Hello';
-e syntax OK
上記の例では、if 1 という条件が 定数畳み込み(Constant Folding)によってコンパイル時に最適化され、実行用のコード上では単なる print 文として扱われています。
ワンライナーで書かれた複雑なコマンドが、どのように解釈されているかを確認するのにも役立ちます。
$ perl -MO=Deparse -p -e 's/foo/bar/'
LINE: while (defined($_ = <ARGV>)) {
s/foo/bar/;
}
continue {
die "-p destination: $!\n" unless print $_;
}
-e syntax OK
-p オプションが、実際には while ループと continue ブロックを展開している様子が確認できます。
Optreeの可視化:B::Concise
さらに低レイヤーの情報を確認する場合は、B::Concise を使用します。これは、Perl仮想マシン(VM)が実行する命令(OPコード)のツリー構造を表示します。
$ perl -MO=Concise -e '$a = $b + 1'
6 <@> leave[1 ref] vKP/REFC ->(end)
1 <0> enter ->2
2 <;> nextstate(main 1 -e:1) v:{ ->3
5 <2> sassign vKS/2 ->6
3 <2> add[t3] sK/2 ->4
- <1> ex-rv2sv sK/1 ->-
- <1> gvsv(*b) s ->-
- <0> const[IV 1] s ->-
- <1> ex-rv2sv sKRM*/1 ->5
- <1> gvsv(*a) s ->-
これがPerlにとっての「アセンブリ言語」に相当します。
スタックマシンの挙動として、gvsv で変数をスタックに積み、add で加算し、sassign で代入するといった処理の流れを追うことができます。
コンパイラバックエンドの活用
B モジュールを利用することで、独自のバックエンド処理を記述することも可能です。CPANにはこれを利用した様々なツールが存在します。
-
静的解析:
B::Lint
コンパイル時にコードの問題点を検出するツール -
他の言語への変換:
B::C
PerlコードをC言語に変換するプロジェクト
まとめ
- Perlは内部でOptree(Opcode Tree)という中間表現にコンパイルされてから実行される
-
perl -MO=Deparseを使うと、Perlの解釈・最適化結果をソースコードとして確認できる -
perl -MO=Conciseを使うと、実行されるOPコードを確認でき、パフォーマンスチューニングなどに役立つ
普段書いているコードが内部でどのように変換されているかを知ることで、より効率的なコード記述にも繋がります。
次回は、Perlの中からC言語を直接記述して実行する Inline::C について記載します。