2008年01月24日

Perl界隈で熱いと噂のautoboxの実装を調べたりtokuhiromパッチを発展させてみました。
autoboxはsexy。

このエントリのまとめ

本文が長過ぎるので先にまとめを書く。

autoboxはPerlの内部実装をhackして、とてもスマートに実装されたモジュールだというのがわかりました。
一カ所だけアクロバティックな事してますが、それ以外は無理無く実装されていて普通に使う分には十分使えるものっぽいというのが判った。

tokuhiromのパッチの件

tokuhiromのパッチはINTEGERとかFLOATとかSTRINGを別けて扱える様になって良いんだけど、既存のSCALARを拡張したautobox::Encodeとかが動かなくなってとても困るので更にパッチを書いた。
取得はhttp://tech.yappo.jp/tmp/autobox-tokuhirom-yappo-2.patchから。

やっている事としては、オブジェクトがINTEGER,FLOAT.STRINGの時に、それぞれのパッケージにメソッドや変数の定義をしていない場合にSCALARを利用するようになります。
細かく書くとautoboxをimportした時に利用するパッケージにisaとcanのメソッドを生やすので、例えばINTEGERの場合には%INTEGER::にエントリが2個以下しか無い時は、SCALARを使うようにしています。

@ISAにSCALARを突っ込めば良いじゃんという話もありますが、@ISAに突っ込んでしまうと意図しないメソッドまで継承してしまう可能性があるので、INTEGERパッケージにメソッド等が定義されていなければ、利用するパッケージをSCALARパッケージに切り替えるという事をやっています。
もし継承させたいなら、利用者が明示的に

@INTEGER::ISA = qw( SCALAR )
するというポリシーです。

とりあえず既存のautoboxのテストが動く状態になったので、追加された要素のテストを書いてchocolateboyにパッチを送ってみようと思うのですが、いかがでしょうか。
2個以下の時はSCALARってのがad hoc過ぎる気がするのが気がかりですが。

ここから本編

物凄くざっくりとコードを読んだ時にメモしたりしたので、もったいないので公開しときます。
だいぶザックリとしてるので信憑性に問題があるかもしれません。
それでも問題無いよ、という人だけ読んで下さい。

autobox.pm

autobox.pmは、importでレファレンスタイプをどのパッケージに割り当てるのか、hintsを使ってuse autoboxをしている側のローカルスコープの中だけでautoboxされる様に良きにはらってくれる。
unimportも実装されてるけど使いどころを良く知らない。

ではautobox::importの処理を簡単に追っていきましょう。

use autobox SCALAR => 'MySCALAR'
等でuseされた場合には、"hoge"->foo;された場合にはSCALAR::fooを呼ぶのではなくMySCALAR::fooを呼ぶ。

hintsの操作もimportでやっている。
0x20000 だけでいいんだけど %^H 周りのバグがあるらしくて 0x100000 も追加しておかないといけない。
これをやる事により %^H の中身がスコープの中でしか有効にならない。
もっというと use autobox されたスコープの中でのみ有効になる。

$^Hとか%^Hのいわゆるhint情報は、コンパイラが動作する為のヒント情報なのでBEGINフェーズ中でしか意図どおりに動かない。
下記のコードは意図どおりになるが

use strict;
use warnings;
BEGIN {
    $^H |= 0x120000;
    $^H{hoge} = 'start';
}

BEGIN{warn $^H{hoge}}
{
    BEGIN{$^H{hoge} = 'YYY';warn $^H{hoge}}
    BEGIN{warn $^H{hoge}}
}
BEGIN{warn $^H{hoge}}
下記のコードは意図どおりにならない
use strict;
use warnings;
$^H |= 0x120000;
$^H{hoge} = 'start';
warn $^H{hoge}
{
    $^H{hoge} = 'YYY';warn $^H{hoge};
    warn $^H{hoge};
}
warn $^H{hoge}

最後にautobox.xsの中にあるenterscopeを呼び出す。
enterscopeは後述するが、perl内部のフックをautobox用に入れ替える。
遂になる物としてはleavescopeがあり、これはautoboxのスコープが抜けた時に元に戻す処理をしている。
スコープが抜けたという事は0x20000のhint bitsと%^HとScope::Guadを上手く組み合わせて実装している。

Scope::Guadというのは以下のような事をやります。

if (1) {
    my $sg = Scope::Guad->new(sub { } ); # DESTROYした時に呼ばれるコードを登録
    # ... 処理
}# ここで登録したコードが呼ばれる
インスタンスがDESTROYした時にnew時に設定したコードを実行するというシンプルな物です。

use autoboxされたスコープを抜けるとScope::GuadのインスタンスもDESTROYされる事になり、Autobox::leavescopeが呼ばれる事になる。
なんでDESTROYされるかというと、%^Hの中にScope::Guadのインスタンスを入れとくので、スコープ抜けると%^Hのなかがクリアされるので、Scope::GuadのインスタンスがDESTROYされるのである。

importの中に書かれたらimportのスコープを抜けた時点でDESTROYされるじゃないか。という突っ込みもあるかもしれないが、useされた時はどうやら別物らしい。

autobox.xs

次はいよいよautobox.xsの中身だ!
autobox.xsにはAutoboxというpackageに属する、enterscope,leavescope,ENDという3個のメソッドが実装されている。
ENDは、いわゆるENDフェーズのアレだから省略する。

leavescope

autoboxの影響化を抜けた時に後述するenterscopeで入れ替えたメソッドを元に戻す。

enterscope

Autobox::enterscopeは、use autoboxされた時に呼び出される、autobox::importの最後の処理でenterscopeを呼び出している。
何をやっているかというと、PerlのVMのとあるフェーズで実行される処理を差し替えている。
具体的にPerl本体のコードで言うと、opcode.hの

EXT OP *(CPERLscope(*PL_check)[]) (pTHX_ OP *op) = {
が書かれているPL_checkというリストの中にフックテーブルがあって、そのフックテーブルを入れ替えている。
フックテーブル/VMのオペコードにどんなのがあるかはopcode.hとopnames.hを見ること、これはテストにでるよ。
PL_checkが何なのかは調べてない。何となくコンパイルフェーズでチェックするフックポイントテーブルみたいなのだと思ってる。

何を差し替えているかというと、OP_METHOD_NAMEDというOO的なcodeで指定されたmethodが存在するかといったチェックとかをやるフェーズっぽい。
本来はop.cのPerl_ck_nullが登録されてるので、何をやるべきかわかってない。
で、autobox.xsのautobox_ck_method_namedに差し替えられている。

autobox_ck_method_named

use autoboxされたスコープの影響範囲にいなければ本来の処理と同じ挙動になる。
影響範囲にいる時には、$^H{autobox}の中に入っている handler table をPL_hintgvから取り出しておき、autoboxの胆となる処理が走る。

autobox_method_named

このメソッドがautoboxの肝。
ここで呼ばれたメソッドがどういう形("hoge"->foo, 1->foo, []->foo, {}->foo)で呼ばれるのか、何のリファレンスタイプで呼ばれてるのかを調べている、具体的にはtokuhiromのpatchしてる辺り。
そして、リファレンスタイプに対応するhandler tableに登録されてる実際に使われるpackage nameを取り出す。
gv_stashpvnというのは指定されたパッケージ名に紐づけられたhashを取り出すもの、Perlのコードでいう所の%SCALAR::みたいな物。gv.cを見よ。
そしてごにょごにょ処理をする。

gv_fetchmethod(stash ? stash : (HV*)packsv, name);
の所では、パッケージの中にメソッド定義されているかを調べる感じ。

autobox_method_namedでもっとも大事な部分というのはXPUSHsでgvをpushしてる所。
スタックを操作することにより、

1->foo;
といったpackage nameでもなくオブジェクトリファレンスでも無いコードに対して、1という部分がSCALARというオブジェクトレファレンスなんだよと錯覚させてしまうのである。と思う。スタック型なんだなぁというのがわかる。

メソッドが見つからなければ

return PL_ppaddr[OP_METHOD_NAという所にいく。
これはメソッドがねーぞというエラーを出すことと同等。

autobox_ck_subr

OP_ENTERSUBというフェーズも書き換えている、本来はop.cのPerl_ck_subrというメソッドを呼んでおり、処理内容を見るとprototype宣言のパースをやってる。
何となくだけど、autobox_ck_method_namedで変更したフラグを戻すとかやってるのかな?
これといって大げさな処理は行われていない。

おまけ

以前encoding::sourceを5.8で動かそうとして無理だった件で、%^Hの値がレキシカルスコープ担ってないからってのがあったんだけど、autoboxを見たらhint bitsに0x20000を立てとけば同じような挙動になるのが解った。
それなんでdan.pmとかencoding::sourceをdorからdefined or にcallerの10個目のを%^Hに、import/unimportで0x20000の出し入れをやってみたけど、うまく動かなかった。
callerの件が実装されてないので、もしかしたらBEGINフェーズ以外でも%^Hが必要になってるのかもしれない。
こんど改めて調べてみることにする。

ちなみに、まだinstall autoboxしてない。

Posted by Yappo at 2008年01月24日 20:20 | TrackBack | Perl
Comments

s/遂に/対に/
s/::Guad/::Guard/g
# Dan the Typo Fixer

Posted by: at 2008年01月25日 09:09
Post a comment









Remember personal info?






コメントを投稿する前に↓の場所にnospamと入力してください。