[インデックス 17508] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)における「internal error: typename ideal bool」という内部エラーを修正するものです。具体的には、型推論と定数評価の過程で発生する型の不整合を解消し、コンパイラの安定性を向上させます。この修正は、Go言語のIssue 6298で報告されたバグに対応しています。
コミット
- コミットハッシュ:
a7d8b35aacf00a87953a67c9d1f793c16a5440ef
- Author: Russ Cox rsc@golang.org
- Date: Mon Sep 9 12:00:16 2013 -0400
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a7d8b35aacf00a87953a67c9d1f793c16a5440ef
元コミット内容
cmd/gc: fix 'internal error: typename ideal bool'
Fixes #6298.
R=ken2
CC=golang-dev
https://golang.org/cl/13624043
変更の背景
このコミットは、Goコンパイラが特定のコードパターンを処理する際に発生していた「internal error: typename ideal bool」という内部エラーを解決するために行われました。このエラーは、コンパイラの型チェックフェーズにおいて、ideal bool
(理想的なブール型)という特殊な型が予期せぬ形で扱われた結果として発生していました。
Go言語では、リテラル(例: true
, false
, 1
, 1.0
, "hello"
)は、その値が特定の型に割り当てられるまで「型なし(untyped)」として扱われます。ブールリテラルも同様に「型なしブール(untyped boolean)」として扱われ、これはコンパイラ内部で「ideal bool」として表現されることがあります。この「ideal bool」は、最終的にbool
型に変換されることを意図していますが、変換前の段階で誤った処理が行われると、コンパイラが予期しない状態に陥り、内部エラーを引き起こす可能性がありました。
Issue 6298の報告によると、interface{}
型への型なしブール値の代入がこのエラーをトリガーしていました。具体的には、var x interface{} = "abc"[0] == 'a'
のようなコードが問題を引き起こしていました。ここで"abc"[0] == 'a'
は型なしブール値(true
)を生成しますが、これがinterface{}
に代入される際に、コンパイラの内部処理で型の不整合が発生し、typename ideal bool
というエラーメッセージが出力されていました。
このエラーはコンパイラのバグであり、ユーザーが記述したGoコードが正当であるにもかかわらず、コンパイルが失敗するという問題を引き起こしていました。そのため、コンパイラの堅牢性を高め、このような内部エラーを回避するための修正が必要とされました。
前提知識の解説
Goコンパイラ (cmd/gc
)
cmd/gc
は、Go言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担っています。コンパイルプロセスは、字句解析、構文解析、型チェック、中間コード生成、最適化、コード生成など、複数のフェーズに分かれています。このコミットが関連するのは、主に型チェックと中間コード生成のフェーズです。
型なし定数 (Untyped Constants) と Ideal Types
Go言語には「型なし定数(untyped constants)」という概念があります。これは、数値リテラル(例: 10
, 3.14
)、ブールリテラル(例: true
, false
)、文字列リテラル(例: "hello"
)など、明示的な型が指定されていない定数のことです。これらの定数は、使用される文脈に応じて適切な型に「推論」されます。
例えば、var i int = 10
の場合、10
は型なし整数定数ですが、int
型に割り当てられます。var f float64 = 10
の場合、同じ10
がfloat64
型に割り当てられます。
コンパイラ内部では、これらの型なし定数を表現するために「理想型(ideal types)」という概念が使われます。
ideal int
: 型なし整数定数ideal float
: 型なし浮動小数点数定数ideal bool
: 型なしブール定数ideal string
: 型なし文字列定数
これらの理想型は、Goの組み込み型(int
, float64
, bool
, string
など)とは異なり、コンパイル時のみに存在する抽象的な型です。型なし定数が変数に代入されたり、関数に引数として渡されたりする際に、コンパイラは理想型から具体的なGoの型への変換(型推論)を行います。
walk.c
と evconst
src/cmd/gc/walk.c
は、Goコンパイラのバックエンドの一部であり、抽象構文木(AST)を走査(walk)し、中間表現(IR)に変換する処理を担当しています。このフェーズでは、定数式の評価、型チェックの最終調整、最適化などが行われます。
evconst
関数(evaluate constant
の略)は、walk.c
内で呼び出される重要な関数の一つで、与えられたノード(ASTの要素)が定数式である場合に、その値を評価し、結果を定数リテラルノードに変換する役割を担っています。例えば、1 + 2
のような式があれば、evconst
はこれを評価して3
という定数リテラルに変換します。
typecheck
typecheck
関数は、Goコンパイラの型チェックフェーズにおいて、式の型が正しいかどうかを検証し、必要に応じて型推論や型変換を行う役割を担っています。この関数は、ASTノードの型情報を設定・更新します。
技術的詳細
問題の根源は、walk.c
内のevconst
関数の呼び出しと、その後のノードの型情報の扱いにありました。
元のコードでは、evconst(n)
が呼び出される前にノードn
の型が一時変数に保存されていませんでした。evconst(n)
は、ノードn
が定数式であれば、そのノードのop
(操作)をOLITERAL
(リテラル)に変更し、そのtype
(型)を評価結果の型(例えばideal bool
からbool
)に更新します。
しかし、evconst
が呼び出された後、n->op == OLITERAL
のチェックが行われ、その条件が真であればtypecheck(&n, Erv)
が再度呼び出されていました。このtypecheck
の呼び出しが問題を引き起こしていました。
typecheck
は、ノードの型情報に基づいて処理を行います。evconst
によってノードn
の型がideal bool
のような特殊な型に更新された後、typecheck
が再度呼び出されると、typecheck
がideal bool
という型を予期しない文脈で受け取ってしまい、内部エラー「typename ideal bool」が発生していました。
これは、evconst
がノードの型を「最終的な型」に設定するのではなく、定数評価のための一時的な型に設定していたにもかかわらず、その後のtypecheck
がその一時的な型を「最終的な型」として扱おうとしたために発生した競合状態のようなものでした。
修正の目的は、evconst
がノードの型を変更しても、その後のtypecheck
が元のノードの型情報に基づいて正しく動作するようにすることです。
コアとなるコードの変更箇所
--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -1386,7 +1386,9 @@ ret:
// constants until walk. For example, if n is y%1 == 0, the
// walk of y%1 may have replaced it by 0.
// Check whether n with its updated args is itself now a constant.
+ t = n->type;
evconst(n);
+ n->type = t;
if(n->op == OLITERAL)
typecheck(&n, Erv);
コアとなるコードの解説
変更はsrc/cmd/gc/walk.c
のwalkexpr
関数(またはそれに近い場所)内で行われています。
-
t = n->type;
evconst(n)
を呼び出す前に、現在のノードn
の型情報(n->type
)を一時変数t
に保存します。これは、evconst
がノードの型を変更する可能性があるため、元の型情報を失わないようにするためです。
-
evconst(n);
- ノード
n
に対して定数評価を実行します。この関数は、n
が定数式であれば、そのop
をOLITERAL
に変更し、n->type
を評価結果の型に更新します。例えば、"abc"[0] == 'a'
のような式の場合、evconst
はこれをtrue
というブールリテラルに評価し、n->op
をOLITERAL
に、n->type
をbool
(またはideal bool
)に設定する可能性があります。
- ノード
-
n->type = t;
evconst(n)
の呼び出し後、ノードn
の型を、ステップ1で保存しておいた元の型t
に戻します。- この操作が重要です。
evconst
は定数評価のためにノードの型を一時的に変更するかもしれませんが、その後のtypecheck
は、ノードが定数リテラルになったかどうかを判断するために、元の型情報に基づいて動作する必要があります。evconst
がn->type
をideal bool
のような特殊な型に設定した場合でも、typecheck
が呼び出される前に元の型に戻すことで、typecheck
が予期しないideal bool
型を受け取って内部エラーを発生させることを防ぎます。
-
if(n->op == OLITERAL)
- ノード
n
が定数リテラルになったかどうかをチェックします。evconst
が成功した場合、n->op
はOLITERAL
になります。
- ノード
-
typecheck(&n, Erv);
- ノード
n
が定数リテラルになった場合、再度typecheck
を呼び出します。このtypecheck
は、定数リテラルになったノードの最終的な型を確定させるために必要です。しかし、この呼び出しの前にn->type = t;
で元の型に戻されているため、typecheck
は正しいコンテキストでノードの型を処理でき、typename ideal bool
エラーは発生しなくなります。
- ノード
この修正により、evconst
による一時的な型変更が、その後のtypecheck
の動作に悪影響を与えることがなくなり、コンパイラの内部エラーが解消されました。
関連リンク
- Go Issue 6298: https://golang.org/issue/6298
- Go CL 13624043: https://golang.org/cl/13624043