2012年9月30日日曜日

言語C でスコープドポインタを実現する(その1)

gccの拡張機能に頼ることになるが、finstrument-functions機能を使用すると、スコープドポインタもどきが作れる。
これは、関数が呼び出されたときと復帰するときに、2つの特殊な関数
  • void __cyg_profile_func_enter(void *this_fn, void *call_site);
  • void __cyg_profile_func_exit(void *this_fn, void *call_site);
にフックする関数トレース機能を利用する。

原理

任意のブロックというわけにはいかないが、関数内で作られたスコープドポインタを関数から復帰するときに削除する。
スコープドポインタには予め参照先のオブジェクトのデストラクタを設定しておき、スコープドポインタを削除するに先立って参照先のデストラクタを呼び出す。
なお、このプロジェクトでは scoped_ptrという名前ではなく、lwdパッケージの Referenceクラスを用い、「参照」と呼ぶことにする。
その実、この「参照」クラスはスコープドポインタだけではなくシェアードポインタの機能も持たせてある。
例によってこれは実験段階で、幾つかのケースでしか動作が確認できていない。

関数が呼ばれたとき

__cyg_profile_func_enterの中で、つぎのようなことを行う。
関数内で作成された参照を記憶するリストを作成する。
このリストは呼び出しごとに作成されるため、関数を再帰的に呼び出してもどの呼び出しレベルで作られたものか区別することができる。
private boolean suppressed;

private ArrayList frames;

typedef struct Frame* Frame;

struct Frame {
        void* functionAddress;
        ArrayList references;
};

suppressedは、Referenceクラス自身をトレースして循環に陥らないようにするためのフラグ。
フックの処理を開始するときにセットし、抜けるときにリセットする。
このフラグがセットされているときに再びフック関数が呼ばれたときは Referenceの処理中ということで、フック内の処理を全てスキップする。
予め、つぎを定義しておき、
#define NO_INSTRUMENT __attribute__((no_instrument_function))
Referenceクラスの内部の関数には NO_INSTRUMENTを指定してフック関数が呼ばれないようにしているが、Referenceクラス内部で使用している softiesライブラリの関数がトレース対象となるのを防止している。

framesリストは、関数が呼ばれるたびに Frameオブジェクトを追加し、復帰するたびに削除されるリストである。
マルチスレッドで使えるようにスレッドローカルにすべきだが、現在はそうなっていない。
Frameクラスには、関数のアドレスとそのなかで作成された参照のリストが含まれている。関数内で作成される参照の数は不定のため固定長配列ではなく可変長リストの ArrayListクラスを用いている。
Frameクラスは Referenceクラスの内部クラスとして実装されており、無名パッケージに属している。

それでは実際のフック関数をつくってみる。
void NO_INSTRUMENT __cyg_profile_func_enter(void* functionAddress,
        void* callSite)
{
        if (!suppressed) {
                suppressed = true;
                pushNewFrame(functionAddress);
                suppressed = false;
        }
}

pushNewFrame関数が、関数が呼ばれたときのリスト作成を行っている。
private void pushNewFrame(void* functionAddress) {
        Frame frame = new_Frame(functionAddress);
        ArrayList_add(frames, frame);
}

Frameのコンストラクタはこんな感じ。
private Frame new_Frame(void* functionAddress) {
        Frame this = (Frame)malloc(sizeof(struct Frame));

        if (this != null) {
                this->functionAddress = functionAddress;
                this->references = new_ArrayList();
        }

        return this;
}
これで参照の管理の準備ができた。

その2につづく。

心地良いさ、納戸に伝わる君の I love you.