Transcript libgcj4
コンパイラの解析 (4)
例外処理
Suguru ARAKAWA
Faculty of Computer and Information Sciences,
Hosei University
1
例外処理
通常のコントロールフローではない
大域脱出をおこなう
どこからでもジャンプする可能性がある
言語の実装からは隠蔽されている
何か特殊な仕掛けが必要!
2
例外処理の実装方法
二返戻値法
正常値と例外値の両方を返す
通常のコーリングシーケンス+例外検査
setjmp法
例外が発生したら大域脱出
大域脱出のジャンプ先をあらかじめ指定
表引き法
例外が発生したら表を元にジャンプ
正常実行時にほとんどコストが掛からない
3
サンプルプログラム – Add
public static void main(String[] args) {
try {
int result = add(args[0], args[1]);
System.out.println("result = " + result);
}
catch (NumberFormatException e) {
System.out.println(e.getMessage());
System.out.println("unknown result");
}
}
static int add(String a, String b) {
return Integer.parseInt(a) + Integer.parseInt(b);
}
4
二返戻値法における実装 (概要)
関数は常に2つの値を返す
通常の関数の戻り値
(正常値)
例外情報
例外情報が存在(=例外が発生)しているか関
数呼び出しのたびに調査
通常のコーリングシーケンスを利用できる
関数コールのたびに例外検査を行う
5
二返戻値法における実装 (throw)
フラグと情報を用意してリターンすればよい
int parseInt(const char *s) {
char *end;
int a = strtol(s, &end, 10);
if (end[0] != '\0') {
sprintf(exc.message, "parse error: %s", s);
exc.occurred = 1;
return 0;
}
return a;
}
6
二返戻値法における実装 (throws)
例外を通過させる場合も明示的に
関数呼び出しのたびに行う
int add(const char *a, const char *b) {
int ia, ib;
ia = parseInt(a);
if (exc.occurred) return 0;
ib = parseInt(b);
if (exc.occurred) return 0;
return ia + ib;
}
7
二返戻値法における実装 (catch)
例外が発生していた場合にその処理を行う
正常終了時も検査だけは必須!
int main(int argc, char **argv) {
int result = add(argv[1], argv[2]);
if (!exc.occurred) {
printf("result = %d\n", result);
}
else {
puts(exc.message);
puts("unknown result");
}
return 0;
}
8
二返戻値法における実装の特徴
可搬性が高い
高級言語で明示的に例外パスを記述
遅い
正常終了でも例外検査が必要
9
setjmp法における実装 (概要)
setjmp/longjmpを用いて大域脱出
例外情報は別に保管される
そもそも、setjmpって有名?
10
setjmp/longjmpとは (1)
longjmpを呼ぶとsetjmpへワープ
08: int main(int argc, char **argv) {
09: int result = setjmp(jmp);
10: printf("result = %d\n", result);
11: if (result == 0)
12: jump();
13: return 0;
14: }
16: void jump() {
17: puts("begin jump()");
18: longjmp(jmp, 6502);
19: puts("end jump()");
20: }
main:09
main:10
main:11
main:12
jump:17
jump:18
main:10
main:11
main:13
int result = setjmp(jmp);
printf("result = %d\n", );
if (result == 0)
jump();
puts("begin jump()");
longjmp(jmp, 6502);
printf("result = %d\n",…);
if (result == 0)
return 0;
result = 0
begin jump()
result = 6502
11
setjmp/longjmpとは (2)
setjmpの実装
その時点のレジスタを保存する
最初に呼ばれたときには0を返す
longjmpの実装
setjmpで保存したレジスタを復帰
PCやスタックフレーム(SP,
FP)なども巻き戻す
setjmpの結果として引数の値を返す
一部、volatileでないレジスタは消失
12
setjmp法における実装 (catch)
try~の部分でif (setjmp(…) == 0)
int main(int argc, char **argv) {
if (setjmp(jmp) == 0) {
int result = add(argv[1], argv[2]);
printf("result = %d\n", result);
}
else {
puts(message);
puts("unknown result");
}
return 0;
}
13
setjmp法における実装 (throws)
longjmpで大域脱出するのでなにもしない
スキップする
int add(const char *a, const char *b) {
return parseInt(a) + parseInt(b);
}
14
setjmp法における実装 (throw)
値だけ準備してlongjmpすればよい
第2引数で例外の番号を指定できる
int parseInt(const char *s) {
char *end;
int a = strtol(s, &end, 10);
if (*end != '\0') {
sprintf(message, "parse error: %s", s);
longjmp(jmp, 1);
}
return a;
}
15
setjmp法における実装の特徴
可搬性が高い
大抵のC言語はsetjmpをサポートしている
コンパイラが混乱する
volatileの指定がない変数は消失するかも
コンパイラによってはsetjmpがあると最適化抑止
遅い
tryの度にレジスタを退避する
16
表引き法における実装 (概要)
プログラムは次のものを含む
実行可能な部分
(本来のプログラム)
例外表
---------------------
---------------------
---------------------
---------------------
---------------------
--------------------17
表引き法に必要な情報
適用範囲
どこで発生した例外に対応するか
着陸地点
どのアドレスにジャンプするか(catchの位置)
レジスタ情報
Spillしたレジスタはどこに格納されているか
その他
どの種類の例外をキャッチできるか、など
18
表引き法における実装 (throw)
例外を発生させ、キャッチするフレームを探す
自分と呼び出し元の表を参照
キャッチするフレームまで巻き戻す
-----catch(…) {
---}
---------------------
-----raise Exception
-----
---------------------
---------------------
---------------------
19
表引き法における実装 (throws)
表に「例外を通過させる」ことを記述する
何も書かないとthrowsになる実装もある
リソースの解放が必要になる場合が多いので、通
常はリソース解放コードに着地させる
Frap
From
try_begin
Trap
To
try_end
Trap
Type
<any>
Landing
Point
unwind
20
表引き法における実装 (catch)
表に「例外をキャッチする」ことを記述する
キャッチできる型、ハンドラのアドレスを記述
実装によってはキャッチできる型を記述しない
Frap
From
try_begin
Trap
To
try_end
Trap
Type
Exception
Landing
Point
catch1
try_begin
try_end
Error
catch2
try_begin
try_end
<any>
<unwind>
21
表引き法における実装の特徴
高速
正常処理時にコストが掛からない
可搬性が低い
ライブラリ/アーキテクチャごとに仕様が異なる
(通常は)高級言語レベルで記述できない
22
gcjの例外
基本的には表引き法を使う
言語ごとに別の記法を取る
C++/gccも一部同じ機構を利用
高度な記述ができる
スピルされたレジスタの復帰
インライン関数の擬似フレーム記述
23
gcjの例外情報
LSDA (Language Specific Data Address)
トラップ範囲、着地地点、トラップ型のテーブル
FDE (Frame Description Entry)
構築されたフレームに関する情報
退避されたレジスタなどが保存されている位置
詳しくは後述
CIE (Common Information Entry)
幾つかのFDEに共通する情報
FDEと同じような記述もできる
24
LSDAの情報
Header
Call Site Table
キャッチ開始位置,
範囲, 着地地点
使用するAction Tableのエントリ
Action Table
Trap
Type Tableのエントリを解釈する順序
Trap Type Table
キャッチする型の情報
25
LSDAの記述
例外処理のサンプルプログラム.doc
11. bridge関数本体 (i386 - #1)
図 18. LSDAの差分 (i386 - #2)
図 24. LSDA (SPARC - #1)
図 30. LSDAの差分 (SPARC - #2)
図
26
CIEの情報
ヘッダ
使用する拡張情報
(i386=1)
フレームデータの整列単位 (i386=-4)
戻り値の擬似レジスタ番号 (i386=%eip(8))
コードの整列単位
拡張情報
フレーム情報 (CFA)
関数開始時のスタックポインタ、リターンアドレス
27
CIEの記述
例外処理のサンプルプログラム.doc
13. CIE (i386 - #1)
図 25. CIE (SPARC - #1)
図
28
FDEの情報
ヘッダ
このFDEを使用する関数の範囲
対応するCIEの位置
対応するLSDAの位置
フレーム情報 (CFA)
退避されたレジスタなど、全ての情報
29
FDEの記述
例外処理のサンプルプログラム.doc
14. FDE (i386 - #1)
図 26. FDE (SPARC - #2)
図
30
CFA (Canonical Frame Address)
フレーム内のレジスタの位置を記述する
レジスタごとに擬似レジスタ番号が振られ、それらが
フレーム内のどこにあるか記述できる
例外が発生したPCごとに細かく指定できる
記述用のインタープリタが内蔵されている
31
CFAの記述能力
レジスタの位置を記憶するインタープリタ
現在のPCにおける、スピルされたレジスタの退避先
を記述できる
// 古いフレームポインタを退避した以降ならば
advanve_loc4 .LbridgePrologue1
// フレームアドレスの位置は offset(8)
def_cfa_offset offset=8
// レジスタebpをoffset(2)へ退避
offset reg=%ebp(5) offset=2
// プロローグ終了後
advanve_loc4 .LbridgeBody
// フレームアドレスの位置は レジスタ%ebp内
def_cfa_register reg=%ebp(5)
32
資料
http://vtable.rat.cis.k.hosei.ac.jp/nakata/
解析>例外処理
報告資料
libgcjを用いた例外処理に関する報告.doc
例外処理のサンプルプログラム.doc
33
二返戻値法ブリッジ (1)
表引き法はコンパイラやアーキテクチャに依存す
るため、実装が困難
それでも高速に実行できるので利用されている
表引き法のコンパイラを二返戻値法に変換す
る方法を紹介
34
二返戻値法ブリッジ (2)
常に例外をハンドルして、第二値として返せば
よい
ただしThread
Local Storageを利用すること
public Object bridge(Method m, Object obj, Object[] args) {
try {
return m.invoke(args);
}
catch (Throwable t) {
exc.occurred = t;
return null;
}
}
35
二返戻値法ブリッジ (3)
メソッドを呼び出す場合は必ずブリッジ経由
戻ったら必ず第二値の検査
// 実際にはJavaではこの書き方はできない
Object result = bridge(
&Hoge.main,
null,
new String[]{});
if (exc.occurred != null) {
// 例外処理
}
36