Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 192] ファイルの概要

このコミットは、初期のGoコンパイラ(特にAMD64アーキテクチャ向けの6gコンパイラ)におけるバグ修正です。具体的には、switch文においてdefaultケースが最初のケースとして記述された場合に、コンパイラが誤った診断メッセージ(到達不能なステートメントに関する警告)を出力する問題を解決しています。

変更は主にGoコンパイラの2つのC言語ソースファイル、src/cmd/6g/gen.csrc/cmd/gc/walk.cに対して行われています。gen.cはコード生成フェーズ、walk.cは抽象構文木(AST)の変換や高レベル構文の低レベル化(desugaring)を行うウォークフェーズに関連しています。

コミット

commit 1926fef175dc89079f39b952aa02487d5b6e1aeb
Author: Ken Thompson <ken@golang.org>
Date:   Wed Jun 18 11:43:50 2008 -0700

    bogus diagnostic with
    default as first case
    in a switch
    
    SVN=123398

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/1926fef175dc89079f39b952aa02487d5b6e1aeb

元コミット内容

bogus diagnostic with
default as first case
in a switch

SVN=123398

変更の背景

このコミットは、Go言語の初期開発段階におけるコンパイラのバグ修正です。Go言語のswitch文は、他のC系の言語とは異なり、暗黙的なフォールスルー(breakがない場合に次のcaseに処理が移ること)がありません。各caseブロックは独立しており、通常はcaseがマッチするとそのブロックが実行され、switch文全体から抜けます。defaultケースは、どのcaseにもマッチしなかった場合に実行されるブロックです。

問題は、defaultケースがswitch文の最初のcaseとして記述された場合に発生していました。コンパイラが、このdefaultケースの後に続くステートメントを誤って「到達不能(unreachable)」と診断し、不適切な警告やエラーを出力していたと考えられます。これは、コンパイラのコード解析ロジックが、defaultケースが最初に評価される可能性を正しく考慮していなかったためと考えられます。この「誤った診断(bogus diagnostic)」を修正することが、このコミットの目的です。

前提知識の解説

Go言語のswitch

Go言語のswitch文は、式の結果に基づいて複数の実行パスを選択するための制御構造です。主な特徴は以下の通りです。

  • 暗黙的なフォールスルーなし: CやC++とは異なり、Goのswitch文ではcaseブロックの終わりにbreak文を明示的に記述する必要がありません。各caseブロックは、実行が完了すると自動的にswitch文を終了します。
  • fallthroughキーワード: 明示的に次のcaseブロックに処理を移したい場合は、fallthroughキーワードを使用します。
  • defaultケース: どのcaseにもマッチしなかった場合に実行されます。defaultケースは任意の場所に配置できますが、通常は最後に記述されます。このコミットの文脈では、defaultが最初に配置された場合のコンパイラの挙動が問題となりました。
  • switchと型switch: Goのswitch文には、式を評価する通常のswitchと、インターフェースの動的な型をチェックする型switchがあります。このコミットは一般的な式switchに関連するものです。

Goコンパイラの構造(初期)

Goコンパイラは、ソースコードを機械語に変換する複雑なソフトウェアです。2008年当時の初期のGoコンパイラは、主にC言語で書かれていました(後にGo言語自身で書き直されました)。このコミットで触れられているファイルは、その初期コンパイラの重要な部分を構成していました。

  • 6g: これは、Go言語の初期バージョンにおいて、AMD64(64ビットx86)アーキテクチャ向けのコンパイラ実行ファイルの名前でした。同様に、8gは386(32ビットx86)向けでした。現在のGoでは、これらのアーキテクチャ固有のコンパイラは単一のgo tool compileコマンドに統合されています。
  • src/cmd/gc/walk.c: このファイルは、コンパイラの「ウォーク(walk)」フェーズの一部です。ウォークフェーズは、構文解析によって生成された抽象構文木(AST)を走査し、高レベルなGo言語の構文をより低レベルな中間表現に変換(desugaring)したり、式の評価順序を決定したりする役割を担います。例えば、switch文は、このフェーズでジャンプテーブルや二分探索のようなよりプリミティブな操作に変換されることがあります。
  • src/cmd/6g/gen.c: このファイルは、コンパイラの「コード生成(generation)」フェーズの一部です。ウォークフェーズで変換された中間表現を受け取り、最終的な機械語コードやアセンブリ命令を生成する責任を負います。

到達不能コードの診断

コンパイラは、プログラムの品質と正確性を保証するために、到達不能なコード(実行されることが決してないコード)を検出して警告またはエラーを出すことがあります。到達不能コードは、論理的な誤りやデッドコードを示唆するため、開発者にとって重要な情報です。Goコンパイラは、この種の診断に対して比較的厳格です。しかし、このコミットが示すように、初期のコンパイラには、特定の状況下で誤って到達不能コードを報告するバグが存在していました。

技術的詳細

このコミットの技術的詳細は、Goコンパイラの内部動作、特にswitch文の処理におけるコード生成とASTウォークのロジックに深く関わっています。

src/cmd/6g/gen.cの変更

gen.cにおける変更は、swgen関数内で行われています。この関数はswitch文のコード生成を担当していると考えられます。

--- a/src/cmd/6g/gen.c
+++ b/src/cmd/6g/gen.c
@@ -440,7 +440,7 @@ swgen(Node *n)
  	while(c1 != N) {
  		dynlineno = c1->lineno;	// for diagnostics
  		if(c1->op != OCASE) {
- 			if(s0 == C)
+ 			if(s0 == C && dflt == P)
  				yyerror("unreachable statements in a switch");
  			gen(c1);

変更点: if(s0 == C)if(s0 == C && dflt == P)に変更されています。

  • s0 == C: これは、switch文の開始点、またはcaseブロックの開始点を示すフラグであると推測されます。Cは特定の定数値を表す可能性があります。
  • dflt == P: これは、defaultケースが既に処理されたかどうか、またはdefaultケースが存在するかどうかを示すフラグであると推測されます。Pも特定の定数値を表す可能性があります。

元のコードif(s0 == C)は、switch文の開始直後(または特定の条件で)に到達不能なステートメントを検出する一般的なロジックだったと考えられます。しかし、defaultケースが最初に配置された場合、そのdefaultケースが常に到達可能であるにもかかわらず、この条件が真となり、誤って「unreachable statements in a switch」という診断メッセージを出力してしまっていた可能性があります。

新しい条件if(s0 == C && dflt == P)は、この診断をより厳密にしています。dflt == Pという条件が追加されたことで、「defaultケースがまだ処理されていない、または存在しない状況で、かつs0 == Cの条件が満たされる場合のみ」というように、到達不能コードの検出条件が絞り込まれました。これにより、defaultケースが最初に配置された場合でも、その後のコードが正しく到達可能であると判断され、誤った診断が抑制されるようになりました。

src/cmd/gc/walk.cの変更

walk.cにおける変更は、loopラベル内のswitch文の処理に関連しています。

--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -116,6 +116,7 @@ loop:
  		walktype(n->ninit, Etop);
  		walktype(n->ntest, Erv);
  		walktype(n->nbody, Etop);
+\
  		// find common type
  		if(n->ntest->type == T)
  			n->ntest->type = walkswitch(n, sw1);
@@ -127,11 +128,9 @@ loop:
  		// set the type on all literals
  		if(n->ntest->type != T)
  			walkswitch(n, sw3);
-\
-\		walktype(n->ntest, Erv);
-\
-\		n = n->nincr;
-\		goto loop;
+\		walktype(n->ntest, Erv);	// BOTCH is this right
+\		walktype(n->nincr, Erv);
+\		goto ret;
  
  	case OEMPTY:
  		if(top != Etop)

この変更は、switch文のASTウォークにおける制御フローと、n->nincr(おそらくforループのインクリメント部分や、switch文のイテレーションに関連するノード)の処理方法を変更しています。

元のコードでは、walktype(n->ntest, Erv);の後にn = n->nincr; goto loop;というパターンがありました。これは、switch文の各caseを処理した後に、次のcaseや関連するノードに進むためのループ処理の一部だった可能性があります。

変更後、n = n->nincr; goto loop;が削除され、代わりにwalktype(n->nincr, Erv);goto ret;が追加されています。

  • walktype(n->ntest, Erv); // BOTCH is this right: この行は元々存在していましたが、BOTCH is this rightというコメントが追加されています。これは、この部分の型チェックや評価が正しいかどうかについて、当時の開発者が疑問を持っていたことを示唆しています。
  • walktype(n->nincr, Erv);: n->nincrノードに対しても型ウォークが明示的に行われるようになりました。これは、switch文の内部で評価される可能性のある式や変数の型が正しく処理されることを保証するためかもしれません。
  • goto ret;: loopへのジャンプがretへのジャンプに変更されました。これは、switch文の処理が完了した後に、loopの次のイテレーションに進むのではなく、現在の関数や処理ブロックから抜けることを意味します。この変更は、switch文の処理ロジックが、ループの一部としてではなく、より独立したブロックとして扱われるようになったことを示唆しています。

このwalk.cの変更は、switch文のAST処理における内部的なクリーンアップや、gen.cでの診断修正と連携して、switch文のセマンティクスがコンパイラ内で正しく表現されるようにするためのものと考えられます。特に、defaultケースが最初に配置された場合の特殊な挙動を正しく扱うために、ASTウォークの段階で適切な情報がコード生成フェーズに渡されるように調整された可能性があります。

コアとなるコードの変更箇所

diff --git a/src/cmd/6g/gen.c b/src/cmd/6g/gen.c
index b6120d7755..f99114003f 100644
--- a/src/cmd/6g/gen.c
+++ b/src/cmd/6g/gen.c
@@ -440,7 +440,7 @@ swgen(Node *n)
  	while(c1 != N) {
  		dynlineno = c1->lineno;	// for diagnostics
  		if(c1->op != OCASE) {
- 			if(s0 == C)
+ 			if(s0 == C && dflt == P)
  				yyerror("unreachable statements in a switch");
  			gen(c1);
  
@@ -463,7 +463,6 @@ swgen(Node *n)
  		\tdflt = pc;
  
  		while(c2 != N) {
-\
  		\ts = mal(sizeof(*s));
  		\tif(s0 == C)
  		\t\ts0 = s;
diff --git a/src/cmd/gc/walk.c b/src/cmd/gc/walk.c
index 56475b177b..f577a9f708 100644
--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -116,6 +116,7 @@ loop:
  		\twalktype(n->ninit, Etop);
  		\twalktype(n->ntest, Erv);
  		\twalktype(n->nbody, Etop);
+\
  		// find common type
  		if(n->ntest->type == T)
  			n->ntest->type = walkswitch(n, sw1);
@@ -127,11 +128,9 @@ loop:
  		// set the type on all literals
  		if(n->ntest->type != T)
  			walkswitch(n, sw3);
-\
-\		walktype(n->ntest, Erv);
-\
-\		n = n->nincr;
-\		goto loop;
+\		walktype(n->ntest, Erv);	// BOTCH is this right
+\		walktype(n->nincr, Erv);
+\		goto ret;
  
  	case OEMPTY:
  		if(top != Etop)

コアとなるコードの解説

src/cmd/6g/gen.c の変更点詳細

swgen関数は、switch文のコード生成ロジックをカプセル化しています。この関数内で、コンパイラはswitch文の各casedefaultブロックを処理し、対応する機械語命令を生成します。

変更された行は以下の通りです。

- 			if(s0 == C)
+ 			if(s0 == C && dflt == P)
  				yyerror("unreachable statements in a switch");
  • 変更前 (if(s0 == C)): この条件は、switch文の特定の状態(おそらく、まだcaseブロックが開始されていない、または特定の初期状態)で、その後に続くステートメントが到達不能であると判断していました。しかし、defaultケースがswitch文の最初のcaseとして記述された場合、Goのセマンティクスではdefaultケースは常に到達可能であるため、この条件が誤って真となり、不適切な「unreachable statements」エラーを発生させていました。
  • 変更後 (if(s0 == C && dflt == P)): dflt == Pという追加条件が導入されました。ここで、dfltdefaultケースの存在や処理状況を示す内部フラグであると推測されます。Pは、defaultケースがまだ設定されていない、または特定の初期状態にあることを示す値かもしれません。この変更により、コンパイラは「s0 == Cの状態であり、かつdefaultケースがまだ処理されていない(または存在しない)場合にのみ」到達不能コードの診断を行うようになりました。これにより、defaultケースが最初に記述された場合でも、その後のコードが正しく到達可能であると判断され、誤った診断が回避されます。

また、src/cmd/6g/gen.cの別の箇所で、空行が削除されています。これは機能的な変更ではなく、コードの整形やクリーンアップの一環と考えられます。

src/cmd/gc/walk.c の変更点詳細

walk.cの変更は、switch文のASTウォーク処理における制御フローの調整です。

-\
-\		walktype(n->ntest, Erv);
-\
-\		n = n->nincr;
-\		goto loop;
+\		walktype(n->ntest, Erv);	// BOTCH is this right
+\		walktype(n->nincr, Erv);
+\		goto ret;
  • 変更前: walktype(n->ntest, Erv);の後に、n = n->nincr; goto loop;というコードがありました。これは、switch文のテスト式(n->ntest)の型ウォークを行った後、n->nincrノード(おそらくswitch文のイテレーションや次の要素へのポインタ)を更新し、loopラベルにジャンプして処理を続行するパターンでした。これは、switch文が何らかのループ構造の一部として扱われていたか、あるいはswitch文の各caseが連続的に処理されるような内部的なイテレーションロジックがあったことを示唆しています。
  • 変更後: n = n->nincr; goto loop;が削除され、代わりにwalktype(n->nincr, Erv);goto ret;が追加されました。
    • walktype(n->ntest, Erv); // BOTCH is this right: この行は変更されていませんが、// BOTCH is this rightというコメントが追加されています。これは、この特定の型ウォークがswitch文のセマンティクスに対して本当に適切であるか、あるいはより良い方法があるのではないかという開発者の疑問を示しています。初期のGoコンパイラ開発における試行錯誤の痕跡です。
    • walktype(n->nincr, Erv);: n->nincrノードに対しても明示的に型ウォークが実行されるようになりました。これにより、switch文の内部で評価される可能性のあるすべての関連ノードが、型チェックフェーズで適切に処理されることが保証されます。
    • goto ret;: loopへのジャンプがretへのジャンプに変更されました。これは、switch文の処理が完了した後に、現在の処理ブロック(関数など)から抜けることを意味します。この変更は、switch文の処理が、より独立した、自己完結型のユニットとして扱われるようになったことを示唆しており、gen.cでの診断修正と連携して、switch文のセマンティクスがコンパイラ内でより正確に表現されるようにするための調整と考えられます。

これらの変更は、Goコンパイラの初期段階におけるswitch文の内部処理の成熟度を高め、特定のコーナーケース(defaultが最初のcaseである場合)での誤った診断を排除することを目的としていました。

関連リンク

参考にした情報源リンク