[インデックス 18493] ファイルの概要
このコミットは、Goコンパイラのcmd/gc
(現在のcmd/compile
に相当)における、Plan 9オペレーティングシステム上での浮動小数点例外およびメモリ読み取りフォルトのハンドリングに関するものです。具体的には、src/cmd/gc/lex.c
ファイルに、これらの例外を捕捉し、適切に処理するためのcatcher
関数と、その関数をnotify
システムコールに登録するロジックが追加されています。
コミット
commit 9c767b64ee46ccdf6d483946ad5bffe967574989
Author: David du Colombier <0intro@gmail.com>
Date: Thu Feb 13 16:35:51 2014 +0100
cmd/gc: catch notes on Plan 9
LGTM=rsc
R=rsc, jas, gobot
CC=ality, golang-codereviews
https://golang.org/cl/51650051
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9c767b64ee46ccdf6d483946ad5bffe967574989
元コミット内容
cmd/gc: catch notes on Plan 9
LGTM=rsc
R=rsc, jas, gobot
CC=ality, golang-codereviews
https://golang.org/cl/51650051
変更の背景
このコミットの背景には、GoコンパイラがPlan 9オペレーティングシステム上で動作する際の安定性と堅牢性の向上が挙げられます。特に、浮動小数点演算における無効な操作(例: 0による除算、NaNの生成など)や、不正なメモリアクセス(読み取りフォルト)が発生した場合に、コンパイラが予期せずクラッシュするのを防ぐことが目的です。
Plan 9では、Unix系のシグナルとは異なる「notes」というメカニズムを用いて、プロセスへの非同期通知や例外処理を行います。Goコンパイラがこれらのnotesを適切に捕捉し、エラーとして処理することで、コンパイラの実行が中断されることなく、よりユーザーフレンドリーなエラーメッセージを提供できるようになります。
具体的には、浮動小数点例外は、コンパイラがコードを生成する際に発生する可能性があり、これが未処理のままになるとコンパイラが異常終了します。また、メモリ読み取りフォルトは、コンパイラの内部処理におけるバグや、不正な入力データによって発生する可能性があり、これもまたクラッシュの原因となります。このコミットは、これらの特定のnotesを捕捉し、コンパイラが制御を失うことなく、適切なエラー処理パスに進むことを可能にします。
前提知識の解説
Plan 9 from Bell Labs
Plan 9 from Bell Labsは、ベル研究所で開発された分散オペレーティングシステムです。Unixの後継として設計され、その設計思想は「すべてがファイルである」という原則に基づいています。プロセス間通信、デバイスアクセス、ネットワーク通信など、システム内のあらゆるリソースがファイルシステムを通じて抽象化されます。
Plan 9の大きな特徴の一つは、Unixのシグナルに代わる「notes」という例外通知メカニズムです。notesは、プロセスに対して非同期に送信される文字列メッセージであり、プロセスはnotify
システムコールを使ってnoteハンドラを登録できます。noteハンドラは、特定のnoteが到着した際に実行されるコールバック関数です。
Goコンパイラ (cmd/gc
)
cmd/gc
は、Go言語の公式コンパイラの一部であり、Goソースコードを機械語に変換する役割を担っていました。現在のGoのツールチェインでは、この部分はcmd/compile
という名前で提供されています。コンパイラは、字句解析、構文解析、型チェック、中間コード生成、最適化、最終的な機械語コード生成といった複数のフェーズを経て動作します。
浮動小数点例外 (Floating Point Exceptions)
浮動小数点演算において、数学的に定義できない結果や、表現できない結果が生じた場合に発生する例外です。一般的なものとしては、以下のような種類があります。
- Invalid Operation (無効な操作): 0/0、NaN + X、sqrt(-1) など、数学的に意味のない操作。
- Division by Zero (ゼロ除算): 有限の非ゼロ数をゼロで割る操作。
- Overflow (オーバーフロー): 結果が表現可能な最大値を超える場合。
- Underflow (アンダーフロー): 結果が表現可能な最小値よりもゼロに近い場合(非正規化数を含む)。
- Inexact (不正確): 結果が正確に表現できない場合(丸めが必要な場合)。
これらの例外は、通常、CPUの浮動小数点ユニット(FPU)によって検出され、オペレーティングシステムに通知されます。
メモリ読み取りフォルト (Memory Read Fault)
プロセスが、アクセス権のないメモリ領域や、存在しないメモリ領域からデータを読み取ろうとした場合に発生するエラーです。これは、ポインタの不正な使用、配列の範囲外アクセス、解放済みメモリへのアクセスなど、プログラミングエラーによって引き起こされることがほとんどです。オペレーティングシステムは、このような不正なアクセスを検出すると、通常、プロセスにシグナル(Unixの場合)やnote(Plan 9の場合)を送信して通知します。
noted
システムコール
Plan 9におけるnoted
システムコールは、noteハンドラ内で使用され、noteの処理方法をシステムに通知します。
NCONT
: noteを処理した後、通常の実行を継続します。NDFLT
: noteのデフォルトの動作を実行します。通常、プロセスを終了させます。
技術的詳細
このコミットは、Plan 9環境下でのGoコンパイラの堅牢性を高めるために、src/cmd/gc/lex.c
ファイルに例外ハンドリングロジックを追加しています。
catcher
関数の導入
catcher
関数は、Plan 9のnotify
システムコールに登録されるnoteハンドラとして機能します。この関数は、システムから送信されるnote(文字列メッセージ)を受け取り、その内容に基づいて異なる処理を行います。
void
catcher(void *v, char *s)
{
USED(v); // v は未使用であることを明示
// 浮動小数点例外の捕捉
if(strncmp(s, "sys: fp: invalid operation", 26) == 0) {
noted(NCONT); // 実行を継続
return;
}
// メモリ読み取りフォルトの捕捉
if(strncmp(s, "sys: trap: fault read", 21) == 0) {
// エラーが既に存在する場合は終了
if(nsavederrors + nerrors > 0)
errorexit();
fatal("fault"); // 致命的なエラーとして終了
}
noted(NDFLT); // その他のnoteはデフォルト動作
}
sys: fp: invalid operation
: このnoteは、浮動小数点演算で無効な操作が発生したことを示します。catcher
関数はこれを捕捉し、noted(NCONT)
を呼び出すことで、コンパイラの実行を継続させます。これは、コンパイラが浮動小数点例外を無視して処理を続行できることを意味します。Goコンパイラは、通常、浮動小数点演算の結果がNaNやInfになることを許容し、それらをエラーとして扱わない場合があります。このハンドリングにより、コンパイラがこれらの例外でクラッシュするのを防ぎます。sys: trap: fault read
: このnoteは、不正なメモリ読み取りアクセスが発生したことを示します。catcher
関数はこれを捕捉し、既にコンパイラがエラーを検出しているかどうかを確認します。- もし既にエラー(
nsavederrors + nerrors > 0
)が存在する場合、errorexit()
を呼び出して、コンパイラを正常に終了させます。これは、メモリフォルトが既存のエラーの副作用である可能性があり、これ以上処理を続けても意味がないと判断されるためです。 - エラーがまだ存在しない場合、
fatal("fault")
を呼び出して、コンパイラを致命的なエラーとして終了させます。これは、メモリフォルトが予期せぬ深刻な問題を示しており、これ以上実行を継続することが危険であると判断されるためです。
- もし既にエラー(
- その他のnotes: 上記のいずれのnoteにも一致しない場合、
noted(NDFLT)
を呼び出し、システムにnoteのデフォルトの動作を実行させます。これは通常、プロセスを終了させることを意味します。
notify
システムコールへの登録
main
関数内で、#ifdef PLAN9
ディレクティブを使用して、Plan 9環境でのみcatcher
関数をnotify
システムコールに登録するロジックが追加されています。
#ifdef PLAN9
notify(catcher);
#endif
これにより、GoコンパイラがPlan 9上で起動する際に、上記のcatcher
関数がnoteハンドラとして設定され、浮動小数点例外やメモリ読み取りフォルトが発生した場合に、catcher
関数が呼び出されるようになります。
Unix系システムとの比較
Unix系システムでは、signal(SIGSEGV, fault)
のようにSIGSEGV
(セグメンテーション違反)などのシグナルを捕捉して処理しますが、Plan 9ではnotify
とnotesという異なるメカニズムを使用します。このコミットは、GoコンパイラがPlan 9のネイティブな例外処理メカニズムに適合するように変更されたことを示しています。
コアとなるコードの変更箇所
src/cmd/gc/lex.c
ファイルに以下の変更が加えられています。
-
catcher
関数の追加: 164行目のfault
関数の直後に、catcher
関数が追加されています。--- a/src/cmd/gc/lex.c +++ b/src/cmd/gc/lex.c @@ -164,6 +164,23 @@ fault(int s) fatal("fault"); } +void +catcher(void *v, char *s) +{ + USED(v); + + if(strncmp(s, "sys: fp: invalid operation", 26) == 0) { + noted(NCONT); + return; + } + if(strncmp(s, "sys: trap: fault read", 21) == 0) { + if(nsavederrors + nerrors > 0) + errorexit(); + fatal("fault"); + } + noted(NDFLT); +} + void doversion(void) {
-
main
関数でのcatcher
関数の登録: 188行目のmain
関数内で、#ifdef PLAN9
ブロック内にnotify(catcher);
が追加されています。--- a/src/cmd/gc/lex.c +++ b/src/cmd/gc/lex.c @@ -188,6 +205,10 @@ main(int argc, char *argv[]) signal(SIGSEGV, fault); #endif +#ifdef PLAN9 + notify(catcher); +#endif + ctxt = linknew(thelinkarch); ctxt->diag = yyerror; ctxt->bso = &bstdout;
コアとなるコードの解説
このコミットの核心は、Plan 9のnoteメカニズムを利用して、Goコンパイラの実行中に発生する特定のシステムレベルのイベント(浮動小数点例外とメモリ読み取りフォルト)を捕捉し、それらに対して適切な回復または終了処理を行うことです。
catcher
関数は、Plan 9のnoteハンドラの典型的な実装パターンに従っています。引数s
は、発生したnoteの種類を示す文字列です。strncmp
を使用して、特定のnote文字列("sys: fp: invalid operation"
と"sys: trap: fault read"
)と照合することで、どの種類の例外が発生したかを識別します。
-
"sys: fp: invalid operation"
に対するnoted(NCONT)
は、この種の例外がコンパイラの動作にとって致命的ではない場合があることを示唆しています。例えば、コンパイラが定数伝播や最適化の過程で浮動小数点演算を行う際に、一時的に無効な結果が生じても、それが最終的なコード生成に影響しない、あるいは後続の処理で適切に扱われる場合です。これにより、コンパイラはクラッシュすることなく、処理を続行できます。 -
"sys: trap: fault read"
に対する処理はより慎重です。メモリ読み取りフォルトは、通常、より深刻な問題(コンパイラのバグ、メモリ破損など)を示します。if(nsavederrors + nerrors > 0)
のチェックは重要です。これは、既にコンパイラが他のエラーを検出している場合に、このメモリフォルトがそのエラーの連鎖反応である可能性を考慮しています。この場合、errorexit()
を呼び出して、既に報告されているエラーと共にコンパイラを正常に終了させます。これにより、無関係なクラッシュレポートを防ぎ、根本原因のデバッグを容易にします。- もし他のエラーが検出されていない状態でメモリフォルトが発生した場合、それは予期せぬ深刻な問題であるため、
fatal("fault")
を呼び出してコンパイラを即座に終了させます。これは、これ以上実行を継続することが不安定であり、さらなる問題を引き起こす可能性があるためです。
main
関数でのnotify(catcher);
の呼び出しは、Goコンパイラが起動する際に、このカスタムハンドラをシステムに登録する役割を果たします。これにより、Plan 9カーネルは、上記の特定のnoteが発生した際に、デフォルトの動作(通常はプロセス終了)を実行する代わりに、catcher
関数を呼び出すようになります。
この変更は、GoコンパイラがPlan 9という特定のオペレーティングシステムの特性に適合し、その環境下での安定性と信頼性を向上させるための、プラットフォーム固有の適応の一例です。
関連リンク
- Plan 9 from Bell Labs: https://9p.io/plan9/
- Plan 9 Notes: Plan 9のnotesメカニズムに関するドキュメントや解説は、Plan 9の公式ドキュメントや関連書籍で詳しく説明されています。
- Goコンパイラのソースコード: 現在のGoコンパイラのソースコードは、GoプロジェクトのGitHubリポジトリで確認できます。
cmd/compile
ディレクトリが関連します。
参考にした情報源リンク
- GoコミットのGitHubページ: https://github.com/golang/go/commit/9c767b64ee46ccdf6d483946ad5bffe967574989
- Go CL (Change List) 51650051: https://golang.org/cl/51650051 (現在はGitHubのコミットページにリダイレクトされる可能性があります)
- Plan 9のドキュメント(notesに関する情報を含む)
- 浮動小数点例外に関する一般的な情報(IEEE 754標準など)
- Goコンパイラの内部構造に関する一般的な知識
- C言語の
#ifdef
プリプロセッサディレクティブに関する知識 strncmp
関数の動作に関する知識fatal
およびerrorexit
といったコンパイラ内部のエラー処理関数に関する一般的な理解 (これらはGoコンパイラの内部実装に依存します)notify
およびnoted
システムコールに関するPlan 9のドキュメント