Hatena::ブログ(Diary)

Islands in the byte stream

2010-02-02

XS基礎文法最速マスター

元ネタ:Perl基礎文法最速マスター(id:perlcodesample)

XSを始めるための手順といくつかの要素の解説です。C言語をある程度知っている人でも,これを読んだだけでXSの基礎をマスターしてXSを書くことができるようにはなっていません。リファレンスでもありません。

XSとは,狭義ではPerlでエクステンションを書くためのマクロ言語の名前ですが,広義ではエクステンションを書くための技術の総称です。ここでは,広義のXSを俯瞰します。

XSはいろいろと特殊なのでテンプレは無視で行きます。

目次:

  1. h2xsで空のディストリビューションを作る
  2. XSファイルの構成
  3. スレッドコンテキスト
  4. SVファミリ
  5. GCとスコープ
  6. さらなる学習のために

h2xsで空のディストリビューションを作る

以下のコマンドで空のXSディストリビューションを作ることができます。

h2xs -A -b 5.8.1 -n Foo::Bar

ディストリビューションを作るためのモジュールはModule::Starter, Module::Setupなどもありますが,最初はh2xsで十分でしょう。なによりインストールの必要がありません。

ディストリビューションを作ったら,その中のディレクトリに移動してとりあえずビルドしてみます。

cd Foo-Bar
perl Makefile.PL
make && make test

これがうまくいかなければコンパイラをセットアップする必要がありますが,省略します。WindowsならStrawberry PerlCygwinを使うのがお勧めです。

次の節に進む前に,XSファイルの先頭,Perlのヘッダファイルをインクルードする前に,"#define PERL_NO_GET_CONTEXT"という行を追加しておきましょう。これはスレッドコンテキストと関係があるのですが,とりあえずオマジナイと考えてかまいません。また,XSセクションには"PROTOTYPES: DISABLE"という行を加えておきましょう。この行がないとXSUBに自動的にプロトタイプがついてしまい,予想外の挙動を招くことになります。

XSファイルの構成

一つのXSファイルはCセクションとXSセクションに分かれています。CセクションはC言語そのもので,モジュールのユーザーからは見えません。XSセクションでXSUBを書き,これはモジュールのユーザーから見えます。

たとえば,以下のようになります。

#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "ppport.h"

=pod

C section

=cut

MODULE = Foo::Bar		PACKAGE = Foo::Bar	

PROTOTYPES: DISABLE	

=pod

XS section

=cut

void
hello()
CODE:
{
    PerlIO_stdoutf("Hello, world!\n");
}

これは,以下のコマンドで実行できます。

make
perl -Mblib -MFoo::Bar -e 'Foo::Bar::hello()'

なお,上の例が示すように,セクションにかかわらず任意の場所にPODを書くことができます*1

スレッドコンテキスト

Cセクションにいろいろ書くつもりがなければ,この節の内容は特に必要ありません。

スレッドコンテキストとは,Perlインタプリタレベルでのグローバル変数を持っている構造体で,ほとんどすべてのPerlAPIの最初の引数として渡すものです。ただし,ほとんどすべてのPerlAPIは,このことを意識しなくてすむようにマクロでラップしてあります。

このことを意識しなければならないのは,Cセクションに関数を書くときです。

このとき,自分の関数についても,PerlAPI と同じように最初の引数にスレッドコンテキスト渡すようにしないと,その関数の内部でPerlAPIが使えません。

スレッドコンテキストの宣言にはpTHX*2/pTHX_*3を使います。API の呼出には aTHX/aTHX_ を使います。

/* in C section */
static void
hello(pTHX) {
    PerlIO_stdoutf("Hello, world!\n");
}

/* IV: Integer Value */
static void
increment(pTHX_ SV* const sv) {
    sv_inc(sv); /* 実際には Perl_sv_inc(aTHX_ sv) に展開される */
}

MODULE = Foo::Bar		PACKAGE = Foo::Bar	

void
hello()
CODE:
{
    hello(aTHX);
}

void
increment(SV* sv)
CODE:
{
    increment(aTHX_ sv);
}

特定のプロトタイプが必要なコールバックなどではスレッドコンテキストを受け取れないことがありますが,その場合は関数の最初で dTHX; 宣言をすることにより,スレッドコンテキストを定義することができます。

SVファミリ

Perlのデータは,CレベルではSV*(スカラー),AV*(配列),HV*(ハッシュ)などで表現されますが,これらすべてはSV*の派生クラスと考えられます。つまり,SvTYPE() や SvREFCNT_inc()/SvREFCNT_dec() などのAPIは,その引数としてどのSVファミリでも受け取れるようになっています。以後,単にSVと書くときは,SVファミリのことと考えてください。

SVファミリを操作するためのAPIの数は多いので紹介はしませんが,一般に,スカラー値であれば sv_*4/Sv*5 というプレフィクスを持ち,同様に配列では av_/Av,ハッシュでは hv_/Hv というプレフィクスを持つので,ソースコードを読むときには頭に入れておくといいでしょう。

なお,SVファミリの具体的な挙動については,perldoc perlapiを参照してください。

GCとスコープ

Perlはリファレンスカウントベースのガベージコレクション機構を持っています。また,スコープと揮発性(mortality)という概念で,XSレベルでもリファレンスカウントの管理を自動化できるようになっています。

XSにおけるスコープは,Perlレベルでのスコープと同じです。これは,ENTER; SAVETMPS; というマクロで開き,FREETMPS; LEAVE; というマクロで閉じます*6

そして,このスコープの中であるSVを揮発性にすると,そのSVのリファレンスカウントは,そのスコープを抜けるときに一つ減ります*7。この処理はスコープの中で例外が起きたときでもきちんと行われるので,手動でリファレンスカウントを減らすよりも安全です。SVを揮発性にするには,sv_2mortal()を使います。初めから揮発性のSVがほしいときは,sv_newmortal()が,SVの揮発性コピーがほしいときはsv_mortalcopy()を使います。

/* in C section */
static void
foo(pTHX) {
    SV* sv;
    ENTER;
    SAVETMPS;

    sv = sv_newmortal();
    sv_setpvs(sv, "Hello, world!\n");
    PerlIO_stdoutf("%"SVf, sv); /* -> "Hello, world!" */

    FREETMPS;
    LEAVE;
    /* ここでsvはすでに解放されている */
}

SvREFCNT_inc()とSvREFCNT_dec()でリファレンスカウントを手動で操作することもできますが,慣れないうちは手動で操作する代わりにsv_mortalcopy()とスコープメカニズムを使ったほうが安全です。

なお,すべてのXSUBは呼び出されるときにスコープで囲まれます。また,XSUBからの戻り値は揮発性のSVでなければならないという呼出規約があります*8。したがって,XSUBからSVを直接返す時は,必ずsv_2mortal()で揮発性にしましょう。

sv_2mortal()を掛けすぎると,Perlインタプリタが「不正にSVの解放が行われた」と文句を言ってくれますが,メモリリークについてはインタプリタは文句を言いません。したがって,不安だったらsv_2mortal()するように習慣づけておくと安全です。

さらなる学習のために

以上,XSを俯瞰しましたが,実際にXSを一から書くのは大変です。いくつかのXSモジュールを実際に読んでみたり,改造してみたり*9することをお勧めします。

Scalar-List-UtilsのXSセクションは比較的理解しやすいのではないかと思います*10

また,Perl自身のソースコードは,その時の最新の PerlAPI *11に則って書かれているため,最良の教科書です。sv.c,av.c,pp_hot.c,universal.c は比較的読みやすく,重要性も高いのでお勧めです。細かな挙動について知るためにはPerlソースコードを参照するしかないことも少なくなく,XSを書くならPerlソースコードを手元に置いておくのは必須といえます。

See also:

Enjoy XS!

*1:使っている例を見たことはありませんが!

*2:唯一の引数として

*3:数ある引数の中の最初のものとして。アンダーバーはカンマに置き換えられると考えてよい。

*4:関数の場合

*5:マクロの場合

*6:なぜそれぞれ二つのマクロが必要なのかについてはきちんとした理由があるのですが,込み入った話になるので省略します。とにかく,ENTERとSAVETMPSのどちらも必要なのです。

*7:そして,リファレンスカウントが0になると当然解放されます

*8:正確には違います。Perlは引数スタックのSVのリファレンスカウントには触らないのです。したがって,作ったSVをそのままスタックに置いておくと,そのSVは解放されません。

*9:「このSVにsv_2mortal()を掛けるとどうなるだろう?」

*10:Cセクションはすべてバージョン間の差異を吸収するための#ifdefです。

*11:古いスタイルのPerlAPIを知る必要はほとんどありません。ppport.hというヘッダファイルを使うことにより,古いバージョンのperlでも最新のPerlAPIを使うことができるからです。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証