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

[インデックス 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の場合、同じ10float64型に割り当てられます。

コンパイラ内部では、これらの型なし定数を表現するために「理想型(ideal types)」という概念が使われます。

  • ideal int: 型なし整数定数
  • ideal float: 型なし浮動小数点数定数
  • ideal bool: 型なしブール定数
  • ideal string: 型なし文字列定数

これらの理想型は、Goの組み込み型(int, float64, bool, stringなど)とは異なり、コンパイル時のみに存在する抽象的な型です。型なし定数が変数に代入されたり、関数に引数として渡されたりする際に、コンパイラは理想型から具体的なGoの型への変換(型推論)を行います。

walk.cevconst

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が再度呼び出されると、typecheckideal 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.cwalkexpr関数(またはそれに近い場所)内で行われています。

  1. t = n->type;

    • evconst(n)を呼び出す前に、現在のノードnの型情報(n->type)を一時変数tに保存します。これは、evconstがノードの型を変更する可能性があるため、元の型情報を失わないようにするためです。
  2. evconst(n);

    • ノードnに対して定数評価を実行します。この関数は、nが定数式であれば、そのopOLITERALに変更し、n->typeを評価結果の型に更新します。例えば、"abc"[0] == 'a'のような式の場合、evconstはこれをtrueというブールリテラルに評価し、n->opOLITERALに、n->typebool(またはideal bool)に設定する可能性があります。
  3. n->type = t;

    • evconst(n)の呼び出し後、ノードnの型を、ステップ1で保存しておいた元の型tに戻します。
    • この操作が重要です。evconstは定数評価のためにノードの型を一時的に変更するかもしれませんが、その後のtypecheckは、ノードが定数リテラルになったかどうかを判断するために、元の型情報に基づいて動作する必要があります。evconstn->typeideal boolのような特殊な型に設定した場合でも、typecheckが呼び出される前に元の型に戻すことで、typecheckが予期しないideal bool型を受け取って内部エラーを発生させることを防ぎます。
  4. if(n->op == OLITERAL)

    • ノードnが定数リテラルになったかどうかをチェックします。evconstが成功した場合、n->opOLITERALになります。
  5. typecheck(&n, Erv);

    • ノードnが定数リテラルになった場合、再度typecheckを呼び出します。このtypecheckは、定数リテラルになったノードの最終的な型を確定させるために必要です。しかし、この呼び出しの前にn->type = t;で元の型に戻されているため、typecheckは正しいコンテキストでノードの型を処理でき、typename ideal boolエラーは発生しなくなります。

この修正により、evconstによる一時的な型変更が、その後のtypecheckの動作に悪影響を与えることがなくなり、コンパイラの内部エラーが解消されました。

関連リンク

参考にした情報源リンク