[インデックス 10655] ファイルの概要
このコミットは、Goコンパイラ(gc
)における定数書き換え(constant rewrites)の挙動を改善し、エラーメッセージの可読性を向上させることを目的としています。具体的には、定数式が評価された後でも、その元の抽象構文木(AST)ノードへのポインタを保持することで、エラーメッセージに評価済みの定数値ではなく、元の式(例: 1 + 2
や unsafe.Alignof(0)
)を表示できるようにします。これにより、開発者はエラーの原因をより正確に把握できるようになります。
コミット
commit 4349effb15b5de82bfa8435c562a01c3d5c116e4
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Wed Dec 7 16:18:50 2011 -0500
gc: keep pointer to original node in constant rewrites.
This allows printing meaningful expressions in error messages
instead of evaluated constants.
Fixes #2276.
R=golang-dev, rsc
CC=golang-dev, remy
https://golang.org/cl/5432082
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4349effb15b5de82bfa8435c562a01c3d5c116e4
元コミット内容
gc: keep pointer to original node in constant rewrites.
This allows printing meaningful expressions in error messages
instead of evaluated constants.
Fixes #2276.
R=golang-dev, rsc
CC=golang-dev, remy
https://golang.org/cl/5432082
変更の背景
この変更は、Goコンパイラが定数式を処理する際に発生していた、エラーメッセージの分かりにくさを解消するために導入されました。具体的には、Go言語のIssue 2276("gc compiler error: confusing error messages for unused constant expression")で報告された問題に対応しています。
Goコンパイラは、コンパイル時に定数式(例: 1 + 2
や unsafe.Alignof(0)
)を評価し、その結果の定数値に置き換える「定数伝播(constant propagation)」や「定数畳み込み(constant folding)」と呼ばれる最適化を行います。この最適化自体はパフォーマンス向上に寄与しますが、問題は、評価後の定数値が元の式に関する情報を失ってしまう点にありました。
例えば、1 + 2
という式が未使用であるというエラーを報告する際、コンパイラが元の式に関する情報を保持していないと、単に「3
が使用されていません」といったメッセージを出力してしまいます。しかし、開発者にとっては「1 + 2
が使用されていません」というメッセージの方が、どのコードが問題なのかを特定しやすく、デバッグの効率が格段に向上します。
このコミットは、このような「意味のある式」をエラーメッセージに表示できるようにするため、定数書き換えの過程で元のASTノードへのポインタを保持するメカニズムを導入しました。
前提知識の解説
Goコンパイラ (gc
)
gc
は、Go言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担っています。コンパイルの過程で、構文解析、型チェック、最適化、コード生成など、様々なフェーズを実行します。
抽象構文木 (AST: Abstract Syntax Tree)
ASTは、ソースコードの構造を木構造で表現したものです。コンパイラはソースコードを直接扱うのではなく、まずASTに変換し、このASTを操作することで様々な処理を行います。各ノードは、変数、関数呼び出し、演算子などの言語要素を表します。
定数伝播 (Constant Propagation) と 定数畳み込み (Constant Folding)
これらはコンパイラの最適化手法の一つです。
- 定数伝播: プログラム中で定数として扱われる値が、その定数を使用する箇所に直接置き換えられる最適化です。
- 定数畳み込み: コンパイル時に評価可能な定数式(例:
1 + 2
)を、その結果の定数値(例:3
)に置き換える最適化です。
これらの最適化により、実行時の計算が不要になり、プログラムのパフォーマンスが向上します。
Node
構造体と n->orig
ポインタ
Goコンパイラの内部では、ASTの各要素が Node
構造体として表現されます。この Node
構造体には、様々な情報が含まれています。
n->orig
は、ある Node
が別の Node
から派生したり、最適化によって書き換えられたりした場合に、元の Node
へのポインタを保持するために使用されるフィールドです。このコミットの目的は、この n->orig
ポインタを適切に管理し、元の式に関する情報を失わないようにすることです。
OLITERAL
ノード
OLITERAL
は、GoコンパイラのASTにおけるノードの種類の一つで、リテラル値(定数)を表します。例えば、数値リテラル 123
や文字列リテラル "hello"
などが OLITERAL
ノードとして表現されます。定数畳み込みによって生成される結果の定数も OLITERAL
ノードになります。
技術的詳細
このコミットの核心は、Goコンパイラの定数書き換え処理において、元のASTノードへの参照を維持することです。これにより、定数式が評価されて OLITERAL
ノードに変換された後でも、その OLITERAL
ノードがどの元の式から派生したのかを追跡できるようになります。
変更は主に src/cmd/gc/const.c
と src/cmd/gc/unsafe.c
の2つのファイルで行われています。
src/cmd/gc/const.c
の変更
const.c
は、Goコンパイラにおける定数式の評価と書き換えを担当する部分です。
以前のコードでは、定数式 n
が評価されて新しいリテラルノード nl
に書き換えられる際、n
の orig
フィールドは単に n->orig
の値を保持していました。しかし、もし n
自体が既に orig
ノードである場合(つまり、n == n->orig
の場合)、書き換えによって n
の内容が nl
の内容で上書きされると、元の式に関する情報が失われてしまう可能性がありました。
このコミットでは、この問題を解決するために、以下のロジックが追加されました。
if(n == n->orig) {
// duplicate node for n->orig.
norig = nod(OLITERAL, N, N);
*norig = *n;
} else
norig = n->orig;
このコードは、もし現在のノード n
がその orig
ノードと同一である場合(つまり、n
が元の式を表すノードである場合)、n
の内容を新しい OLITERAL
ノード norig
に複製します。これにより、n
が nl
の内容で上書きされた後でも、元の式を表す norig
が保持され、その情報が失われるのを防ぎます。
最終的に、書き換えられたノード n
の orig
フィールドには、この norig
が設定されます。
src/cmd/gc/unsafe.c
の変更
unsafe.c
は、unsafe
パッケージに関連するコンパイル時の処理を担当します。特に、unsafe.Alignof
や unsafe.Sizeof
のような関数は、コンパイル時に定数として評価されます。
このファイルでは、unsafe.Alignof(0)
のような式が評価されて新しいリテラルノード n
が生成される際に、その n
の orig
フィールドに元のノード nn
を設定する変更が加えられました。
n->orig = nn;
// ...
nn->type = types[TUINTPTR];
これにより、unsafe
パッケージの関数呼び出しが定数に畳み込まれた場合でも、元の関数呼び出しの式(例: unsafe.Alignof(0)
)が n->orig
を通じて追跡可能になります。また、nn->type = types[TUINTPTR];
の行は、元のノード nn
の型を uintptr
に設定することで、型の一貫性を保ちます。
これらの変更により、コンパイラは定数式を評価した後も、その元の表現に関する情報を保持できるようになり、結果としてより詳細で分かりやすいエラーメッセージを生成することが可能になりました。
コアとなるコードの変更箇所
diff --git a/src/cmd/gc/const.c b/src/cmd/gc/const.c
index 96abf1a655..dd4c4433be 100644
--- a/src/cmd/gc/const.c
+++ b/src/cmd/gc/const.c
@@ -842,8 +842,12 @@ unary:
}
ret:
- norig = n->orig;
- // rewrite n in place.
+ if(n == n->orig) {
+ // duplicate node for n->orig.
+ norig = nod(OLITERAL, N, N);
+ *norig = *n;
+ } else
+ norig = n->orig;
*n = *nl;
// restore value of n->orig.
n->orig = norig;
diff --git a/src/cmd/gc/unsafe.c b/src/cmd/gc/unsafe.c
index 21496b08cc..95200ad415 100644
--- a/src/cmd/gc/unsafe.c
+++ b/src/cmd/gc/unsafe.c
@@ -94,8 +94,10 @@ ret:
val.u.xval = mal(sizeof(*n->val.u.xval));
mpmovecfix(val.u.xval, v);
n = nod(OLITERAL, N, N);
+ n->orig = nn;
n->val = val;
n->type = types[TUINTPTR];
+ nn->type = types[TUINTPTR];
return n;
}
diff --git a/test/fixedbugs/bug379.go b/test/fixedbugs/bug379.go
index 9b93578e53..3dd3d2983b 100644
--- a/test/fixedbugs/bug379.go
+++ b/test/fixedbugs/bug379.go
@@ -7,12 +7,12 @@
// Issue 2452.
// Check that the error messages says
-// bug378.go:17: 3 not used
+// bug378.go:17: 1 + 2 not used
// and not
// bug378.go:17: 1 not used
package main
func main() {
- 1 + 2 // ERROR "3 not used|value computed is not used"
+ 1 + 2 // ERROR "1 \+ 2 not used|value computed is not used"
}
diff --git a/test/fixedbugs/bug381.go b/test/fixedbugs/bug381.go
new file mode 100644
index 0000000000..3f3232bf12
--- /dev/null
+++ b/test/fixedbugs/bug381.go
@@ -0,0 +1,20 @@
+// errchk $G $D/$F.go
+//
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+//
+// Issue 2276.
+//
+// Check that the error messages says
+// bug378.go:19: unsafe.Alignof(0) not used
+// and not
+// bug378.go:19: 4 not used
+
+package main
+
+import "unsafe"
+
+func main() {
+ unsafe.Alignof(0) // ERROR "unsafe\.Alignof|value computed is not used"
+}
コアとなるコードの解説
src/cmd/gc/const.c
の変更点
if(n == n->orig) {
// duplicate node for n->orig.
norig = nod(OLITERAL, N, N);
*norig = *n;
} else
norig = n->orig;
*n = *nl;
// restore value of n->orig.
n->orig = norig;
このコードブロックは、定数書き換えの直前に実行されます。
if(n == n->orig)
: これは、現在のノードn
が、そのorig
フィールドが指すノードと同一であるかどうかをチェックしています。これは、n
が元の式を表すノードであり、まだ他のノードから派生していない状態であることを意味します。norig = nod(OLITERAL, N, N);
: もしn
が元のノードである場合、新しいOLITERAL
型のノードnorig
を作成します。N
はnilポインタを表します。*norig = *n;
: 新しく作成したnorig
ノードに、現在のn
ノードの内容をコピーします。これにより、n
が書き換えられる前に、その元の状態(元の式に関する情報)がnorig
に保存されます。else norig = n->orig;
: もしn
が元のノードではない場合(つまり、既に他のノードから派生している場合)、norig
は単にn
の既存のorig
フィールドの値を引き継ぎます。*n = *nl;
: ここで、現在のノードn
の内容が、評価結果である新しいリテラルノードnl
の内容で上書きされます。n->orig = norig;
: 最後に、書き換えられたn
ノードのorig
フィールドに、ステップ2または4で決定されたnorig
の値を設定します。これにより、n
が定数に書き換えられた後も、そのorig
フィールドを通じて元の式に関する情報にアクセスできるようになります。
src/cmd/gc/unsafe.c
の変更点
n = nod(OLITERAL, N, N);
+ n->orig = nn;
n->val = val;
n->type = types[TUINTPTR];
+ nn->type = types[TUINTPTR];
return n;
このコードブロックは、unsafe
パッケージの関数(例: unsafe.Alignof
)がコンパイル時に定数として評価され、新しいリテラルノード n
が生成される部分です。
n = nod(OLITERAL, N, N);
: 新しいOLITERAL
型のノードn
を作成します。これがunsafe
関数の評価結果である定数を表します。n->orig = nn;
: ここが重要な変更点です。新しく作成されたリテラルノードn
のorig
フィールドに、元のunsafe
関数呼び出しを表すノードnn
を設定します。これにより、定数に畳み込まれた後でも、元のunsafe.Alignof(0)
のような式が追跡可能になります。nn->type = types[TUINTPTR];
: 元のノードnn
の型をTUINTPTR
(unsigned integer pointer type) に設定します。これは、unsafe
パッケージの関数がポインタ関連の操作を行うため、その型情報を正しく反映させるためのものです。
これらの変更により、Goコンパイラは定数式を評価する際に、元の式の情報をより堅牢に保持できるようになり、結果としてよりユーザーフレンドリーなエラーメッセージを提供できるようになりました。
関連リンク
- Go Issue 2276: https://github.com/golang/go/issues/2276 (Web検索結果から推測される関連Issue)
- Gerrit Change-Id:
5432082
(コミットメッセージに記載されているGerritの変更リストID)
参考にした情報源リンク
- GitHub: https://github.com/golang/go/commit/4349effb15b5de82bfa8435c562a01c3d5c116e4
- Web検索: "Go issue 2276" (Goコンパイラのエラーメッセージに関するIssueを特定するため)
- https://github.com/golang/vscode-go/issues/2276 (VS Codeのデバッグモードに関するIssueだが、Goコンパイラのエラーメッセージに関する言及がある)
- https://github.com/golang/go/issues/2276 (Goコンパイラのエラーメッセージに関する直接的なIssue)