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

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

このコミットは、Goコンパイラのsrc/cmd/gc/walk.cファイルに対する変更と、テストファイルの移動を含んでいます。walk.cは、Goコンパイラのフロントエンドの一部であり、抽象構文木(AST)を走査し、型チェック、定数畳み込み、最適化などの処理を行う役割を担っています。

コミット

  • コミットハッシュ: d27e9f528dc1123c8db24fea012eaf2256eb5bcb
  • Author: Ken Thompson ken@golang.org
  • Date: Tue Mar 10 19:16:31 2009 -0700

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/d27e9f528dc1123c8db24fea012eaf2256eb5bcb

元コミット内容

bug086

R=r
OCL=26090
CL=26090
---
 src/cmd/gc/walk.c                  | 4 +++-
 test/{bugs => fixedbugs}/bug087.go | 0
 test/golden.out                    | 5 -----
 3 files changed, 3 insertions(+), 6 deletions(-)

変更の背景

このコミットは、bug086という内部的なバグ報告に対応するものです。具体的なバグの内容はコミットメッセージからは直接読み取れませんが、test/bugs/bug087.gotest/fixedbugs/bug087.goに移動されていることから、bug087.goというテストケースが修正されたバグに関連していると推測されます。

walk.cの変更内容を見ると、文字列リテラルのlen(長さ)の扱いに関する修正が行われています。Go言語において、文字列リテラルの長さはコンパイル時に定数として決定されるべきですが、以前の実装ではこの処理に問題があった可能性があります。特に、evconst(n)の削除と、TSTRINGケースでのnodconstの追加は、文字列リテラルの長さを正しく定数として評価するための修正と考えられます。

前提知識の解説

Go言語の文字列リテラルとlen関数

Go言語において、文字列リテラルはダブルクォーテーション(")で囲まれた一連の文字です。例えば、"hello"は文字列リテラルです。Goの文字列はUTF-8エンコードされており、len関数は文字列に含まれるバイト数を返します。これはUnicodeコードポイントの数とは異なる場合があります(例: 日本語の文字は複数バイトで表現されるため、len("あ")は3を返します)。

重要なのは、Goのコンパイラは文字列リテラルを定数として扱います。したがって、len("some string")のような式は、実行時ではなくコンパイル時にその値(この場合は11)が決定される「定数式」となります。

Goコンパイラのgc(Go Compiler)

gcはGo言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担っています。コンパイルプロセスは複数のフェーズに分かれており、その中に「フロントエンド」と「バックエンド」があります。

  • フロントエンド: ソースコードの字句解析、構文解析、抽象構文木(AST)の構築、型チェック、定数畳み込みなどを行います。
  • バックエンド: ASTを中間表現に変換し、最適化を行い、最終的にターゲットアーキテクチャの機械語を生成します。

src/cmd/gc/walk.cの役割

src/cmd/gc/walk.cは、gcコンパイラのフロントエンドの一部です。このファイルには、ASTを走査("walk")するための関数群が含まれています。ASTの各ノード(変数、関数呼び出し、演算子など)を訪れ、そのノードに対応するセマンティックな処理(型チェック、定数畳み込み、変数の解決など)を実行します。

定数畳み込み(Constant Folding)

定数畳み込みは、コンパイル時に定数式を評価し、その結果を直接コードに埋め込む最適化手法です。例えば、2 + 3という式があれば、コンパイラはこれを5に置き換え、実行時に加算を行う必要がなくなります。これにより、実行時のパフォーマンスが向上します。文字列リテラルのlenも定数畳み込みの対象となります。

技術的詳細

このコミットの主要な変更は、src/cmd/gc/walk.c内のloopラベルが付いたセクションにあります。このセクションは、ASTノードを走査し、そのノードの型に基づいて様々な処理を行う部分です。

変更前は、walktype(n->left, Erv);implicitstar(&n->left);の後にevconst(n);が呼び出されていました。evconst関数は、ノードnが定数式であればその値を評価し、定数ノードに変換する役割を担っていました。しかし、この位置でのevconstの呼び出しは、文字列リテラルのlenのケースにおいて、適切なタイミングで文字列の長さを評価できていなかった可能性があります。

変更後、evconst(n);の呼び出しは削除され、代わりにTSTRING(文字列型)のケースに特化した処理が追加されました。

case TSTRING:
    if(whatis(n->left) == Wlitstr)
        nodconst(n, types[TINT], n->left->val.u.sval->len);
    break;

このコードスニペットは以下のことを行っています。

  1. case TSTRING:: 現在処理しているノードの左の子(n->left)が文字列型である場合にこのブロックに入ります。
  2. if(whatis(n->left) == Wlitstr): whatis関数はノードの種類を判定します。Wlitstrは「文字列リテラル」を意味します。つまり、この条件は、ノードの左の子が文字列リテラルであるかどうかをチェックしています。
  3. nodconst(n, types[TINT], n->left->val.u.sval->len);:
    • nodconst関数は、指定されたノードnを定数ノードに変換します。
    • types[TINT]は、定数ノードの型が整数型(int)であることを示します。これは、len関数の結果が整数であるためです。
    • n->left->val.u.sval->lenは、文字列リテラルノードn->leftの値(val.u.sval)からその長さ(len)を取得しています。

この変更により、コンパイラは文字列リテラルのlenを評価する際に、明示的にその文字列リテラルのバイト長を取得し、その値を整数定数としてノードに設定するようになりました。これにより、len("...")のような式がコンパイル時に正しく定数畳み込みされるようになり、bug086(およびbug087.goでテストされていた問題)が修正されたと考えられます。

test/golden.outからの削除された行は、bugs/bug087.goが以前はコンパイルエラーを出していたことを示しています。このエラーメッセージ「illegal combination of literals LEN 9」は、len関数と文字列リテラルの組み合わせが不正であるとコンパイラが誤って判断していたことを示唆しています。このコミットによって、この誤ったエラーが解消され、テストがfixedbugsディレクトリに移動されたことで、バグが修正されたことが確認できます。

コアとなるコードの変更箇所

diff --git a/src/cmd/gc/walk.c b/src/cmd/gc/walk.c
index d82dfd4ebf..6c93c11cfe 100644
--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -733,7 +733,6 @@ loop:
 		tgoto nottop;
 		twalktype(n->left, Erv);
 		timplicitstar(&n->left);
-		tevconst(n);
 		tt = n->left->type;
 		tif(t == T)
 			tgoto ret;
@@ -741,6 +740,9 @@ loop:
 		tdefault:
 			tgoto badt;
 		tcase TSTRING:
+			tif(whatis(n->left) == Wlitstr)
+				tnodconst(n, types[TINT], n->left->val.u.sval->len);
+			tbreak;
 		tcase TMAP:
 			tbreak;
 		tcase TARRAY:

コアとなるコードの解説

上記の差分は、src/cmd/gc/walk.c内のAST走査ロジックにおける重要な変更を示しています。

  1. evconst(n);の削除:

    • 変更前は、walktypeimplicitstarの後に汎用的な定数評価関数evconstが呼び出されていました。これは、あらゆる種類の定数式を処理しようとするものでしたが、文字列リテラルのlenのケースでは、その評価が不適切であったか、あるいはタイミングが早すぎた可能性があります。
    • この削除により、文字列リテラルのlenの評価は、より具体的なコンテキストで行われるようになります。
  2. TSTRINGケースへの特化された処理の追加:

    • case TSTRING:ブロック内に新しいコードが追加されました。これは、ノードの左の子が文字列型である場合に実行されます。
    • if(whatis(n->left) == Wlitstr): この条件は、左の子が単なる文字列型の変数などではなく、実際にソースコードに書かれた「文字列リテラル」である場合にのみ、以下の処理を実行することを保証します。
    • nodconst(n, types[TINT], n->left->val.u.sval->len);:
      • この行が、文字列リテラルのlenをコンパイル時に定数として評価する核心部分です。
      • nは現在のASTノード(おそらくlen関数呼び出しを表すノード)です。
      • types[TINT]は、len関数の結果が整数型であることを示します。
      • n->left->val.u.sval->lenは、文字列リテラルノード(n->left)から、その文字列のバイト長を直接取得しています。
      • nodconst関数は、この取得したバイト長を定数値としてnノードに設定します。これにより、len("abc")のような式は、コンパイル時に3という定数に置き換えられます。

この変更により、Goコンパイラは文字列リテラルのlenを、より正確かつ効率的にコンパイル時定数として扱うことができるようになりました。これは、コンパイラの堅牢性と最適化能力の向上に貢献しています。

関連リンク

このコミットはGoの初期のバグ修正であり、特定の公開されたIssueやデザインドキュメントへの直接的なリンクは現在のところ見つかっていません。Goのソースコードリポジトリのコミット履歴の一部として参照されます。

参考にした情報源リンク