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

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

このコミットは、Goコンパイラ(gc)における定数書き換え(constant rewrites)の挙動を改善し、エラーメッセージの可読性を向上させることを目的としています。具体的には、定数式が評価された後でも、その元の抽象構文木(AST)ノードへのポインタを保持することで、エラーメッセージに評価済みの定数値ではなく、元の式(例: 1 + 2unsafe.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 + 2unsafe.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.csrc/cmd/gc/unsafe.c の2つのファイルで行われています。

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

const.c は、Goコンパイラにおける定数式の評価と書き換えを担当する部分です。 以前のコードでは、定数式 n が評価されて新しいリテラルノード nl に書き換えられる際、norig フィールドは単に 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 に複製します。これにより、nnl の内容で上書きされた後でも、元の式を表す norig が保持され、その情報が失われるのを防ぎます。 最終的に、書き換えられたノード norig フィールドには、この norig が設定されます。

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

unsafe.c は、unsafe パッケージに関連するコンパイル時の処理を担当します。特に、unsafe.Alignofunsafe.Sizeof のような関数は、コンパイル時に定数として評価されます。 このファイルでは、unsafe.Alignof(0) のような式が評価されて新しいリテラルノード n が生成される際に、その norig フィールドに元のノード 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;

このコードブロックは、定数書き換えの直前に実行されます。

  1. if(n == n->orig): これは、現在のノード n が、その orig フィールドが指すノードと同一であるかどうかをチェックしています。これは、n が元の式を表すノードであり、まだ他のノードから派生していない状態であることを意味します。
  2. norig = nod(OLITERAL, N, N);: もし n が元のノードである場合、新しい OLITERAL 型のノード norig を作成します。N はnilポインタを表します。
  3. *norig = *n;: 新しく作成した norig ノードに、現在の n ノードの内容をコピーします。これにより、n が書き換えられる前に、その元の状態(元の式に関する情報)が norig に保存されます。
  4. else norig = n->orig;: もし n が元のノードではない場合(つまり、既に他のノードから派生している場合)、norig は単に n の既存の orig フィールドの値を引き継ぎます。
  5. *n = *nl;: ここで、現在のノード n の内容が、評価結果である新しいリテラルノード nl の内容で上書きされます。
  6. 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 が生成される部分です。

  1. n = nod(OLITERAL, N, N);: 新しい OLITERAL 型のノード n を作成します。これが unsafe 関数の評価結果である定数を表します。
  2. n->orig = nn;: ここが重要な変更点です。新しく作成されたリテラルノード norig フィールドに、元の unsafe 関数呼び出しを表すノード nn を設定します。これにより、定数に畳み込まれた後でも、元の unsafe.Alignof(0) のような式が追跡可能になります。
  3. 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)

参考にした情報源リンク