例外処理、即ちTry-Catch-Finallyの実装方法をここに記す。
まず始めに、実装に向けて抑えておきたいポイント。
- 大域ジャンプ
- Try専用コールスタック
- スタック領域専用の動的型情報
大域ジャンプとは、ローカル関数を飛び越したGotoのようなもの。一般的に実行ポインタはローカル領域内でのみ変化するものだが、例外に関してはその限りではない。
Try専用コールスタックとは、違う言い方をすればCatch専用コールスタックともとれる。ようは、Catchを検索するためのリスト。スレッド固有な情報であり、Throwされたときのジャンプ先が明記されている。
この2点に関しては、どちらかというと実行制御の問題なので実装に関しては、あまり大きな労力は必要としない。問題となるのは、最後に残った動的型情報。ABコンパイラはコード生成のすべてを静的型情報に頼った形で実現している。実行時、どのような順序で関数が呼び出され、とあるタイミングで構築されるスタック内の詳細情報は不明である。それは、静的に生成した実行コードが自動的に処理してくれるものであり、本来それを知る必要はない。しかし、Throwされたとき、この情報は無くてはならない情報へと扱いが変わる。例外が投げられると、適切なCatchコードへ大域ジャンプすると同時に、スタックが巻き戻される。言ってみみればジャンプ対象となるCatch階層の状態を復元しなければならないのである。そのためには、スタックポインタを巻き戻すだけではなく、そこに蓄積されたオブジェクトインスタンスを解放しなければならない。解放の具体的な手法はデストラクタを呼び出すこと。
スレッド内部は、様々な関数及びメソッドが呼び出されることで常に変化が生じている。そこを静的な覗くことは事実上不可能なので、別途動的型情報として実行時に自動生成しなければならない。
ABコンパイラは、すべての関数に関してその関数が必要なローカル領域の情報をデータ片として管理し、実行時にスレッド固有の動的型情報ストレージに蓄積する。
こうすることで、例外が投げられた場合、安全にローカルオブジェクトの破棄が行われ、スタックは巻き戻される。あとはCatchに実行が遷移するので、その中で適切な例外処理をしていただきたい。
まとめとして、今一度、例外処理のイメージ手順を記しておく。
- Throwされる。
- 遷移先のCatchを検索。
- スタックをどれだけ巻き戻すのかを計算する。
- ロールバック対処のスタック領域内に存在するオブジェクトのデストラクタを新しい順に呼び出す。
- スタックポインタを減算し、Catchのコード位置と同等にする。
- 例外パラメータをスタックに積む。
- 大域ジャンプを行い、Catchコードに実行を移す。
この他、関数やメソッドの入り口と出口では自身のローカル領域に関する型情報を動的に把握するための措置が必要になる。よって、例外処理とは別次元の問題として、下記のような働きを提供しなければならない。
- ローカル領域に配属される実体オブジェクトの型情報とその位置
- 確保されたオブジェクトなのか、未確保のオブジェクトなのかを見分けるためのフラグ管理(モジュールコードの途中でThrowされた場合に必要な情報)
最後に抑えておきたいポイントは同一モジュールローカル領域における実体オブジェクトの存在の問題。例えば、下記のようなコードで解放しなければならないオブジェクト、おおよそ想像がつくだろうか。
Sub Foo() Dim a As Object Throw "messages" Dim b As Object End Sub
Throwされたタイミングではまだbは未確保な状態であり、それに対してデストラクタを呼ぶのは正しい処理とはいえない。しかし、aもbもFoo関数のローカル領域内にあるオブジェクトであり、Foo関数の開始時に動的型情報として専用ストレージへと蓄積される。となると、それとは別途に「これは既に確保されたオブジェクト」「こちらはまだ確保されていないオブジェクト」という具合に状況を把握しておかなければならない。これはコンストラクタ及びデストラクタ呼び出しのタイミングで拡張ストレージ情報として書き込んでいかなければならない。しかし、これをやってしまうと関数呼び出しの前後と同時に、オブジェクト生成のタイミングでもオーバーヘッドが生じてしまう。
ABはネイティブコンパイラであり、高速処理を謳っている数少ないBasic言語なので、このようなオーバーヘッドを生じさせてしまう実装はなるべく避けたいところ。そこで、既存のデータとして存在し、尚且つ実体オブジェクトの存在を確認できる情報はないかと考えてみる。
その答えは、以外な場所にある。Objectクラスだ。Objectは、AB5から実装されるクラスであり、ToStringやEquals、GetHashCodeなどの幾つかの仮想メソッドを持ち合わせる。ここで重要視したいのは、生成されるオブジェクトすべてがvtblポインタを先頭に持つこと。vtblポインタにはコンストラクタで適切なデータがセットされる。関数実行時にローカル領域をすべてクリアできると過程すれば、コンストラクタが呼び出されるまで、そのオブジェクトのvtblは0であることが保障される。それでは、解放された後の状態を知るにはどうすれば良いものか…。答えは至って単純。Objectのデストラクタにvtblに0をセットするようなコードを仕込むだけである。これで、同一モジュール内におけるオブジェクトの存在確認はクリアできる。