[インデックス 192] ファイルの概要
このコミットは、初期のGoコンパイラ(特にAMD64アーキテクチャ向けの6g
コンパイラ)におけるバグ修正です。具体的には、switch
文においてdefault
ケースが最初のケースとして記述された場合に、コンパイラが誤った診断メッセージ(到達不能なステートメントに関する警告)を出力する問題を解決しています。
変更は主にGoコンパイラの2つのC言語ソースファイル、src/cmd/6g/gen.c
とsrc/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
文の各case
とdefault
ブロックを処理し、対応する機械語命令を生成します。
変更された行は以下の通りです。
- 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
という追加条件が導入されました。ここで、dflt
はdefault
ケースの存在や処理状況を示す内部フラグであると推測されます。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
である場合)での誤った診断を排除することを目的としていました。
関連リンク
- Go言語の
switch
文に関する公式ドキュメント: https://go.dev/tour/flowcontrol/9 - Goコンパイラの内部構造に関する一般的な情報(現代のGoコンパイラについてですが、概念は共通しています): https://go.dev/blog/go1.5compiler
参考にした情報源リンク
- Go compiler 6g gen.c walk.c: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFPUisXbG1bHLruydl3ZJ8FvGuo877p4Vma3vzQiwIeVfmxLGDSnqAr_9iXYcTRloNyXuABlHmkxSER_zGw2kSnm8KuEpm6EKmlcxMp0VZgXqn5s7Yti_k3lU_uLeAquQzf3dY3o9C3MuIEju1QLRwgkmD5OXZdGPMBO3hYei76HckmBUdxkpFKFa7yuWgfJ_qR
- Go compiler unreachable statements switch: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHyPInpKSEMWsvgChSvV2lPt1R6L68pi3FdsJ8bQxbyUyUIUABOcuVYxo-PHROsqbW6AHt3l1oX-L9CT-OctLixB54LCY4xzNoYcl2VS7x4H8i20CoYTW_JP8h88dHUDBV4f2Hl8WJC7pi4Fi2asQIyDRERQKzcSz7u4t4X04pv-8AwypPeS3hgTvw4W2II
- Go compiler switch statement default first case bug 2008: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHS7sxqDzxarLrQA7xFvGuo877p4Vma3vzQiwIeVfmxLGDSnqAr_9iXYcTRloNyXuABlHmkxSER_zGw2kSnm8KuEpm6EKmlcxMp0VZgXqn5s7Yti_k3lU_uLeAquQzf3dY3o9C3MuIEju1QLRwgkmD5OXZdGPMBO3hYei76HckmBUdxkpFKFa7yuWgfJ_qR