2012年12月22日土曜日

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

リファレンスの生成

リファレンスの生成は次のように行っている。
Reference NO_INSTRUMENT new_Reference(void* object,
 void (*destructor)(void* object))
{
 Reference this = (Reference)malloc(sizeof(struct Reference));
 if (this != null) {
  this->object = object;
  this->frames = frames;
  this->referenceCounter = 1;
  this->destructor = destructor;
 }
 add(this);
 return this;
}
ヒープ領域からメモリを割り当て、オブジェクトとデストラクタ、framesを記憶し、参照カウンタは1で初期化する。
この実験コードでの framesはグローバル変数であり、複数スレッドに対応していないが、マルチスレッド化するときには framesは TLSに配置される予定である。その場合、Referenceがどの framesに対応しているか素早く探すためのものである。
TLSからキーで検索するならこれは不要である。

つぎに生成したリファレンスを framesに登録するために add関数を呼ぶ。
メモリ割り当てに失敗したときにも addししまうのは暫定のためである。
再帰呼び出しされる関数の中でリファレンスが作られるなら、単純に if文の中に入れれば良いと言うわけにはいかない(復帰時に他の階層のリファレンスを破棄する恐れがある)ので、何らかの配慮が必要になる。

その add関数はつぎの通り。
private void add(Reference r) {
 int top = ArrayList_size(frames) - 1;
 Frame frame = ArrayList_get(frames, top);
 ArrayList_add(frame->references, r);
}

framesリストの先頭にある frameリストを取り出す。
frameリストにリファレンスを追加する。

framesと frameの各リストの構造を(その2)の例題 testAutoCollectを例に図式化すると次のようになる。





洗濯機が貰えるなら僕らもノーベル賞取ろうよ。

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

関数から復帰するとき

前回の使用例のように関数から復帰するときの処理について紹介する。
gccのフック機能を利用してつぎの処理を行う。
void NO_INSTRUMENT __cyg_profile_func_exit(void* functionAddress,
 void* call_site)
{
 if (!suppressed) {
  suppressed = true;
  popFrame(functionAddress);
  suppressed = false;
 }
}
関数呼び出し時同様、自身のトレースを行わないようなフラグ処理を行う。
(ただし、マルチスレッド未対応)
関数呼び出しに対応したフレームをポップする。

popFrameはこんな感じ。
private void popFrame(void* functionAddress) {
 boolean done = false;

 while (!done && ArrayList_size(frames) > 0) {
  int top = ArrayList_size(frames) - 1;
  Frame frame = (Frame)ArrayList_removeAt(frames, top);
  done = (frame->functionAddress == functionAddress);
  Frame_finalize(frame);
 }
}

framesリストの新しいものから順に現在の関数名が現れるまで、古いフレームを捨ててゆく。
こうすると、途中に -finstrument-functionsを指定せずにコンパイルされた関数があってもポップが正しくできる。
また、最低1つはポップ済みのため、関数を再帰的に呼び出しても処理できる。
再起呼び出しの中で以前紹介した例外機構や longjmpによる脱出にも対応可能である。

ただし、落とし穴もある。
-finstrument-functionsを使っていない関数に復帰しただけではすぐに解放処理が行われない。
再起呼び出しにより同じ関数を通ったとしても、条件分岐により catchしたりしなかったりした場合に正しくポップされるかは未だ考慮されていない。
関数アドレスではなく、現在のスタックフレームを比較する必要があるかも知れない。