Released CvGVs could cause SEGV

型グロブの内部表現であるGVは,そのシンボルで表現されるグローバルなスカラー(SV),配列(AV),ハッシュ(HV),サブルーチン(CV),ファイルハンドル(IO)などのオブジェクトを所有するオブジェクトである。
この「所有」とは,実際にSVへの参照(ポインタ)を持っているという意味のほかに,リファレンスカウントを管理する責任があるという意味もある。つまり,GVが解放された時には,所有しているすべてのSVのリファレンスカウントをデクリメントする。

ところで,他のSVファミリと異なり,CVだけはどのGVに所有されているかを知っている。つまり,CV構造体は所有者であるGVを参照するフィールドを持つ。これにより,Sub::Identityが提供するget_code_info()のようなことが可能となっている。

しかし,もしCVもまたGVを「所有」しているとすれば,それは循環参照になってしまう。これを防ぐためには,GVへの参照は「弱い」ものでなければならない。したがって,CVはGVへの参照を持ってはいるが,リファレンスカウントに対する責任は負わないことにしている。いわばウィークリファレンスの簡易版である。これが現在のperlの実装である。

このような実装の都合上,CVがGVの参照を持ったまま参照先のGVが解放されるということは当然起こり得る。

以下のスクリプトでそのような状態を再現できる。
(1)

#!perl -w
use strict;
use Devel::Peek;
my $sub = eval '\&foo';
delete $::{foo};
Dump $sub;
__END__

(2)

#!perl -w
use strict;
use Devel::Peek;
my $sub = sub { 42 };
delete $::{__ANON__};
Dump $sub;

普通のperlであればSegmentation faultに,-DDEBUGGING付のperlならばAssertion failedになるはずだ。
eval()や無名サブルーチンを使っているのは,GVを確実に解放するためである。通常のサブルーチンをそのまま参照すると,そのGVがコンパイルされた構文木からも「所有」されるため,シンボルテーブルから削除されただけではそのGVは解放されないのである。

この問題を解決するためには,CVからのGVの参照を真のウィークリファレンスにする必要がある。しかしウィークリファレンスはそれほど軽くないため,この問題を素朴に解決すると,プログラムのコンパイル速度に悪影響が出るのは必然であろう。何か工夫が必要である。

一つのアイデアは,5.10でHVに対してそうしたように*1,GVにもbackreferencesスロットを新設し,ウィークリファレンスを作りやすくするというものだ。しかし,そうしたとしてもウィークリファレンスのコストはまだ高い。もう少し跳躍したアイデアがほしい。

*1:シンボルテーブル <-> GV間にも循環参照があるため同じ問題を抱えるのだが,こちらは真のウィークリファレンスを導入して解決した。HVへのbackreferencesスロットの新設はそのオーバーヘッドを軽減するためのものであろう。