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

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

コミット

commit 9aef20e823f43eadc2171ea4cf713dddf60cd4dd
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Sat Dec 22 19:16:31 2012 +0100

    cmd/gc: fix wrong interaction between inlining and embedded builtins.
    
    The patch makes the compile user an ordinary package-local
    symbol for the name of embedded fields of builtin type.
    
    This is incompatible with the fix delivered for issue 2687
    (revision 3c060add43fb) but fixes it in a different way, because
    the explicit symbol on the field makes the typechecker able to
    find it in lookdot.
    
    Fixes #3552.
    
    R=lvd, rsc, daniel.morsing
    CC=golang-dev
    https://golang.org/cl/6866047

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

https://github.com/golang/go/commit/9aef20e823f43eadc2171ea4cf713dddf60cd4dd

元コミット内容

cmd/gc: fix wrong interaction between inlining and embedded builtins.

このパッチは、組み込み型(builtin type)の埋め込みフィールド(embedded fields)の名前に対して、コンパイル時に通常のパッケージローカルなシンボルを使用するように変更します。

これは、Issue 2687(リビジョン 3c060add43fb)で提供された修正とは互換性がありませんが、異なる方法で問題を解決します。フィールドに明示的なシンボルを付与することで、型チェッカーが lookdot でそれを見つけられるようになるためです。

Issue #3552 を修正します。

変更の背景

このコミットは、Goコンパイラ(cmd/gc)におけるインライン化(inlining)と組み込み型(builtin types)の埋め込みフィールド(embedded fields)の間の誤った相互作用によって引き起こされるバグを修正することを目的としています。具体的には、Issue #3552として報告された問題に対応しています。

Go言語では、構造体に型を埋め込む(embedding)ことで、その埋め込まれた型のメソッドやフィールドを、あたかも自身のメソッドやフィールドであるかのように直接アクセスできます。また、コンパイラはパフォーマンス向上のために、小さな関数呼び出しを呼び出し元に直接展開するインライン化を行います。

問題は、これらの機能が組み合わさったときに発生しました。特に、builtinpkg(Go言語の組み込み型、例えば intstring などが属するパッケージ)に属する型が構造体に埋め込まれている場合、コンパイラがその埋め込みフィールドのシンボルを正しく解決できないことがありました。これにより、型チェッカーがフィールドを見つけられず、コンパイルエラーや不正なコード生成につながる可能性がありました。

コミットメッセージには、この修正が以前のIssue 2687に対する修正(リビジョン 3c060add43fb)と互換性がないことが明記されています。これは、以前の修正が別の方法で同様の問題に対処しようとしたものの、この新しいアプローチがより根本的な解決策を提供することを示唆しています。新しいアプローチでは、埋め込みフィールドに明示的なパッケージローカルシンボルを割り当てることで、型チェッカーが lookdot(フィールドアクセスを解決するコンパイラの内部関数)で正しくフィールドを識別できるようにします。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびGoコンパイラ(gc)に関する知識が必要です。

  1. Go言語の埋め込み(Embedding): Go言語の構造体は、他の構造体やインターフェースを匿名フィールドとして含めることができます。これを「埋め込み」と呼びます。埋め込まれた型のメソッドやフィールドは、外側の構造体のメソッドやフィールドであるかのように直接アクセスできます。これは、継承とは異なり、型の「合成(composition)」を促進するGoのユニークな機能です。 例:

    type MyInt int
    
    type S struct {
        MyInt // MyInt型を埋め込み
        Name string
    }
    
    func main() {
        s := S{MyInt: 10, Name: "test"}
        fmt.Println(s.MyInt) // MyIntのフィールドに直接アクセス
    }
    
  2. Goコンパイラ gc: gc はGo言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担います。コンパイルプロセスには、字句解析、構文解析、型チェック、中間コード生成、最適化、コード生成などが含まれます。

  3. インライン化(Inlining): コンパイラ最適化の一種で、小さな関数呼び出しを、その関数が呼び出される場所に直接関数の本体のコードを埋め込む(インライン展開する)ことで、関数呼び出しのオーバーヘッドを削減し、パフォーマンスを向上させます。

  4. 組み込み型(Builtin Types)と builtinpkg: int, string, bool などのGo言語に最初から用意されている基本的な型を組み込み型と呼びます。これらの型は、コンパイラの内部では builtinpkg という特別なパッケージに属するものとして扱われます。

  5. シンボル(Symbol): コンパイラがプログラム内の識別子(変数名、関数名、型名など)を内部的に表現するために使用するデータ構造です。シンボルは、その識別子の名前、型、スコープ、メモリ上の位置などの情報を含みます。

  6. lookdot: Goコンパイラの内部関数の一つで、セレクタ式(.演算子を使ったフィールドやメソッドへのアクセス)を解決する役割を担います。例えば、s.MyInt のような式が与えられた場合、lookdots の型定義を調べて MyInt というフィールドが存在するか、または埋め込みフィールドを通じてアクセス可能かを判断します。

  7. パッケージローカルシンボル: Go言語では、識別子の可視性はパッケージによって制御されます。小文字で始まる識別子はパッケージローカルであり、そのパッケージ内からのみアクセス可能です。大文字で始まる識別子はエクスポートされ、他のパッケージからもアクセス可能です。このコミットでは、組み込み型の埋め込みフィールドに対して「通常のパッケージローカルシンボル」を使用することが言及されており、これはコンパイラが内部的にこれらのフィールドをどのように扱うかに関わる変更です。

  8. Issue 2687とリビジョン 3c060add43fb: コミットメッセージで言及されている以前の関連するバグ修正です。このコミットは、その修正とは異なるアプローチを取ることで、より堅牢な解決策を提供しています。

技術的詳細

このコミットの核心は、Goコンパイラ gc が組み込み型(builtinpkg に属する型)の埋め込みフィールドを処理する方法の変更にあります。

以前の gc の実装では、組み込み型の埋め込みフィールドが構造体に含まれている場合、そのフィールドのシンボル解決に問題が生じることがありました。特に、インライン化が行われるような状況で、コンパイラが lookdot を使ってフィールドアクセスを解決しようとすると、正しいシンボルを見つけられないケースがありました。これは、組み込み型が特別な扱いを受けていたため、通常のパッケージローカルなシンボル解決のメカニズムが適用されなかったことに起因すると考えられます。

このコミットでは、この問題を解決するために、src/cmd/gc/dcl.cembedded 関数に修正が加えられています。embedded 関数は、構造体の埋め込みフィールドを処理する際に呼び出されます。変更点として、埋め込みフィールドが builtinpkg に属し、かつ importpkgnil でない場合(つまり、他のパッケージからインポートされた組み込み型の場合)、そのフィールドの名前に対して pkglookup(name, importpkg) を使用して新しいシンボルを作成するようにしています。これにより、組み込み型の埋め込みフィールドであっても、インポート元のパッケージに属する通常のパッケージローカルなシンボルとして扱われるようになります。

この変更により、型チェッカーは lookdot を介してこれらのフィールドを正しく識別できるようになり、インライン化と埋め込み組み込み型の相互作用におけるバグが修正されます。

また、src/cmd/gc/fmt.cexprfmt 関数からも、埋め込み組み込み型に対する特別なフォーマット処理が削除されています。これは、dcl.c でのシンボル解決の変更により、もはや特別な処理が不要になったためです。以前は、l->n->left->type->embeddedtrue の場合に、非修飾名で出力するための特殊なロジックが存在しましたが、このコミットで削除されました。これは、シンボルが正しく解決されるようになったため、通常のフィールドと同様に処理できるようになったことを意味します。

テストケースも更新されており、test/bugs/bug434.go が削除され、test/fixedbugs/issue3552.go および関連するディレクトリが追加されています。これは、新しいテストケースがIssue #3552で報告された具体的なシナリオを再現し、このコミットによる修正が正しく機能することを確認するためのものです。

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

このコミットにおける主要なコード変更は以下のファイルに集中しています。

  1. src/cmd/gc/dcl.c:

    • embedded 関数内で、組み込み型(builtinpkg)の埋め込みフィールドに対するシンボル解決ロジックが追加されました。
      --- a/src/cmd/gc/dcl.c
      +++ b/src/cmd/gc/dcl.c
      @@ -1013,6 +1013,9 @@ embedded(Sym *s)
       
       	if(exportname(name))
       		n = newname(lookup(name));
      +	else if(s->pkg == builtinpkg && importpkg != nil)
      +		// The name of embedded builtins during imports belongs to importpkg.
      +		n = newname(pkglookup(name, importpkg));
       	else
       		n = newname(pkglookup(name, s->pkg));
       	n = nod(ODCLFIELD, n, oldname(s));
      
  2. src/cmd/gc/fmt.c:

    • exprfmt 関数内で、埋め込み組み込み型に対する特別なフォーマット処理が削除されました。
      --- a/src/cmd/gc/fmt.c
      +++ b/src/cmd/gc/fmt.c
      @@ -1082,7 +1082,6 @@ exprfmt(Fmt *f, Node *n, int prec)
       {
       	int nprec;
       	NodeList *l;
      -	Type *t;
       
       	while(n && n->implicit && (n->op == OIND || n->op == OADDR))
       		n = n->left;
      @@ -1208,15 +1207,7 @@ exprfmt(Fmt *f, Node *n, int prec)
       			else
       				fmtprint(f, "(%T{\", n->type);
       			for(l=n->list; l; l=l->next) {
      -				// another special case: if n->left is an embedded field of builtin type,
      -				// it needs to be non-qualified.  Can't figure that out in %S, so do it here
      -				if(l->n->left->type->embedded) {
      -					t = l->n->left->type->type;
      -					if(t->sym == S)
      -						t = t->type;
      -					fmtprint(f, " %hhS:%N", t->sym, l->n->right);
      -				} else
      -					fmtprint(f, " %hhS:%N", l->n->left->sym, l->n->right);
      +				fmtprint(f, " %hhS:%N", l->n->left->sym, l->n->right);
       
       			if(l->next)
       				fmtstrcpy(f, ",");
      
  3. テストファイルの変更:

    • test/bugs/bug434.go が削除され、test/fixedbugs/issue3552.dir/one.gotest/fixedbugs/issue3552.dir/two.go にリネームされました。
    • 新しいテストファイル test/fixedbugs/issue3552.go が追加されました。
    • test/run.go から bugs/bug434.go のスキップリストエントリが削除されました。

コアとなるコードの解説

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

dcl.c はGoコンパイラの宣言処理に関連する部分です。embedded 関数は、構造体内の埋め込みフィールドを処理する際に呼び出されます。

変更前のコードでは、埋め込みフィールドのシンボルを決定する際に、エクスポートされた名前(exportname(name))であるか、または現在のパッケージに属する名前(pkglookup(name, s->pkg))であるかをチェックしていました。

追加された行:

else if(s->pkg == builtinpkg && importpkg != nil)
    // The name of embedded builtins during imports belongs to importpkg.
    n = newname(pkglookup(name, importpkg));

このコードブロックは、以下の条件が満たされた場合に実行されます。

  • s->pkg == builtinpkg: 埋め込みフィールドのシンボル sbuiltinpkg(Goの組み込み型が属するパッケージ)に属している場合。
  • importpkg != nil: 現在のコンパイルコンテキストが、他のパッケージからインポートされたコードを処理している場合。

この条件が満たされると、newname(pkglookup(name, importpkg)) が呼び出されます。

  • pkglookup(name, importpkg): これは、指定された nameimportpkg を使用して、パッケージローカルなシンボルを検索または作成する関数です。これにより、組み込み型の埋め込みフィールドであっても、インポート元のパッケージに属する通常のパッケージローカルなシンボルとして扱われるようになります。
  • newname(...): 新しいシンボルノードを作成します。

この変更の意図は、組み込み型の埋め込みフィールドが、インポートされたコンテキストで正しくシンボル解決されるようにすることです。これにより、コンパイラの型チェッカーが lookdot を使用してこれらのフィールドにアクセスする際に、正しいシンボル情報を見つけられるようになります。

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

fmt.c はGoコンパイラのフォーマット処理に関連する部分で、主にAST(抽象構文木)ノードを文字列として表現する役割を担います。exprfmt 関数は、式ノードをフォーマットする際に使用されます。

変更前のコードには、構造体リテラル(OSTRUCTLIT)のフィールドをフォーマットする際に、埋め込み組み込み型に対する特別な処理がありました。

// another special case: if n->left is an embedded field of builtin type,
// it needs to be non-qualified.  Can't figure that out in %S, so do it here
if(l->n->left->type->embedded) {
    t = l->n->left->type->type;
    if(t->sym == S)
        t = t->type;
    fmtprint(f, " %hhS:%N", t->sym, l->n->right);
} else
    fmtprint(f, " %hhS:%N", l->n->left->sym, l->n->right);

このコードは、l->n->left が埋め込みフィールドであり、かつ組み込み型である場合に、フィールド名を非修飾で出力するための特殊なロジックでした。これは、シンボル解決が不完全であったために必要とされていた回避策です。

今回のコミットでは、dcl.c でのシンボル解決の改善により、この特別な処理が不要になったため、上記の if ブロック全体が削除され、常に以下の一般的なフォーマット処理が適用されるようになりました。

fmtprint(f, " %hhS:%N", l->n->left->sym, l->n->right);

これは、埋め込み組み込み型であっても、通常のフィールドと同様にシンボルが正しく解決され、特別なフォーマット処理が不要になったことを示しています。コードが簡素化され、より一貫性のある処理が可能になりました。

関連リンク

参考にした情報源リンク

  • Go言語の埋め込みに関する公式ドキュメントやチュートリアル
  • Goコンパイラ(gc)の内部構造に関する資料(Goのソースコード、Goのコンパイラに関する論文やブログ記事など)
  • Go言語のシンボル解決、型チェック、インライン化に関する一般的な情報
  • GoのIssueトラッカー(GitHub Issues)
  • GoのGerrit Code Reviewシステム
  • Goのソースコード(特に src/cmd/gc ディレクトリ)
  • Goのテストフレームワークに関する情報(test/run.go など)
  • Goの組み込み型に関する情報I have extracted the commit information and now need to perform web searches to gather more context for the technical explanation.

Here are the searches I plan to make:

  1. "Go gc inlining embedded builtins" - To understand the general problem.
  2. "Go issue 3552" - To get details about the specific bug this commit fixes.
  3. "Go issue 2687" - To understand the previous related issue.
  4. "Go 3c060add43fb" - To find the specific commit mentioned as incompatible.
  5. "Go compiler dcl.c embedded function" - To understand the context of the code change in dcl.c.
  6. "Go compiler fmt.c exprfmt function" - To understand the context of the code change in fmt.c.
  7. "Go compiler lookdot" - To understand what lookdot does.

I will perform these searches in parallel.# [インデックス 14724] ファイルの概要

コミット

commit 9aef20e823f43eadc2171ea4cf713dddf60cd4dd
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Sat Dec 22 19:16:31 2012 +0100

    cmd/gc: fix wrong interaction between inlining and embedded builtins.
    
    The patch makes the compile user an ordinary package-local
    symbol for the name of embedded fields of builtin type.
    
    This is incompatible with the fix delivered for issue 2687
    (revision 3c060add43fb) but fixes it in a different way, because
    the explicit symbol on the field makes the typechecker able to
    find it in lookdot.
    
    Fixes #3552.
    
    R=lvd, rsc, daniel.morsing
    CC=golang-dev
    https://golang.org/cl/6866047

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

https://github.com/golang/go/commit/9aef20e823f43eadc2171ea4cf713dddf60cd4dd

元コミット内容

cmd/gc: fix wrong interaction between inlining and embedded builtins.

このパッチは、Goコンパイラ(cmd/gc)において、インライン化と組み込み型(builtin type)の埋め込みフィールド(embedded fields)の間の誤った相互作用を修正します。

具体的には、組み込み型の埋め込みフィールドの名前に対して、コンパイル時に通常のパッケージローカルなシンボルを使用するように変更します。

この修正は、以前のIssue 2687(リビジョン 3c060add43fb)で提供された修正とは互換性がありませんが、異なる方法で問題を解決します。フィールドに明示的なシンボルを付与することで、型チェッカーが lookdot(フィールドアクセスを解決するコンパイラの内部関数)でそのフィールドを見つけられるようになるためです。

このコミットは、Issue #3552を修正します。

変更の背景

このコミットは、Goコンパイラ cmd/gc における特定のバグ、すなわちインライン化と組み込み型の埋め込みフィールドが組み合わさった際に発生する誤動作を修正するために導入されました。この問題は、GoのIssueトラッカーでIssue #3552として報告されていました。

Go言語では、コードの再利用性と柔軟性を高めるために「埋め込み(embedding)」という機能を提供しています。これにより、ある構造体内に別の型(構造体、インターフェース、または組み込み型)を匿名フィールドとして含めることができ、埋め込まれた型のメソッドやフィールドを外側の構造体から直接アクセスできるようになります。同時に、Goコンパイラはプログラムの実行性能を向上させるために、小さな関数呼び出しをその呼び出し元に直接展開する「インライン化(inlining)」という最適化を行います。

問題は、これらの機能が特定の条件下で衝突することにありました。特に、intstring のようなGoの組み込み型が構造体に埋め込まれ、その埋め込みフィールドへのアクセスがインライン化されるような状況で、コンパイラがそのフィールドのシンボルを正しく解決できないケースが発生していました。これにより、コンパイルエラーや、実行時に予期せぬ動作を引き起こす可能性がありました。

コミットメッセージでは、この修正が以前のIssue 2687に対する修正(リビジョン 3c060add43fb)とは互換性がないと述べられています。これは、過去にも同様の問題に対する試みがあったものの、今回のコミットがより根本的かつ異なるアプローチで解決を図っていることを示唆しています。新しいアプローチでは、埋め込みフィールドに明示的なパッケージローカルシンボルを割り当てることで、コンパイラの型チェッカーが lookdot という内部処理において、これらのフィールドを正確に識別できるようにします。これにより、インライン化が行われても、埋め込み組み込み型へのアクセスが正しく処理されるようになります。

前提知識の解説

このコミットの技術的詳細を深く理解するためには、以下のGo言語およびGoコンパイラ(gc)に関する専門知識が必要です。

  1. Go言語の埋め込み(Embedding): Go言語の構造体は、他の型(構造体、インターフェース、または組み込み型)を匿名フィールドとして含めることができます。このメカニズムを「埋め込み」と呼びます。埋め込まれた型のフィールドやメソッドは、外側の構造体のフィールドやメソッドであるかのように直接アクセス可能です。これは、Goにおける「継承」の代替であり、型の「合成(composition)」を促進する強力な機能です。 例:

    type MyInt int // 組み込み型 int を基にしたカスタム型
    
    type Container struct {
        MyInt // MyInt型を匿名フィールドとして埋め込み
        Name  string
    }
    
    func main() {
        c := Container{MyInt: 42, Name: "Example"}
        fmt.Println(c.MyInt) // ContainerのインスタンスからMyIntの値を直接アクセス
    }
    
  2. Goコンパイラ gc: gc はGo言語の公式コンパイラであり、Goのソースコードを機械語に変換する役割を担います。コンパイルプロセスは複数のフェーズに分かれており、字句解析、構文解析、型チェック、中間表現(IR)生成、最適化、コード生成などが含まれます。このコミットは、主に型チェックと最適化のフェーズに影響を与えます。

  3. インライン化(Inlining): コンパイラ最適化の一種です。関数呼び出しのオーバーヘッド(スタックフレームのセットアップ、引数の渡し、関数へのジャンプなど)を削減するために、呼び出される関数の本体のコードを、その関数が呼び出される場所に直接埋め込む(インライン展開する)技術です。これにより、実行速度が向上し、さらなる最適化(例: 定数伝播、デッドコード削除)の機会が生まれることがあります。Goコンパイラは、関数のサイズや複雑さに基づいて自動的にインライン化を決定します。

  4. 組み込み型(Builtin Types)と builtinpkg: int, string, bool, float64 など、Go言語に最初から定義されている基本的な型を組み込み型と呼びます。これらの型は、コンパイラの内部では builtinpkg という特別なパッケージに属するものとして扱われます。これは、Go言語のコア機能の一部として特別に扱われることを意味します。

  5. シンボル(Symbol): コンパイラがプログラム内の識別子(変数名、関数名、型名など)を内部的に表現するために使用するデータ構造です。シンボルは、その識別子の名前、型、スコープ、メモリ上の位置、可視性などの情報を含みます。コンパイラはシンボルテーブルを管理し、プログラム内の各識別子を一意に識別し、その属性を追跡します。

  6. lookdot: Goコンパイラの内部関数の一つで、セレクタ式(.演算子を使ったフィールドやメソッドへのアクセス)を解決する役割を担います。例えば、obj.Field のような式が与えられた場合、lookdotobj の型定義を調べて Field という名前のフィールドが存在するか、または埋め込みフィールドを通じてアクセス可能かを判断します。このプロセスは、正しいフィールドのメモリ位置を特定し、型チェックを行う上で不可欠です。

  7. パッケージローカルシンボル: Go言語では、識別子の名前の先頭文字が大文字か小文字かによって、その可視性(エクスポートされるか否か)が決定されます。小文字で始まる識別子は、その識別子が定義されているパッケージ内でのみアクセス可能な「パッケージローカル」なシンボルです。大文字で始まる識別子は、他のパッケージからもアクセス可能な「エクスポートされた」シンボルです。このコミットでは、組み込み型の埋め込みフィールドに対して「通常のパッケージローカルなシンボル」を使用することが言及されており、これはコンパイラが内部的にこれらのフィールドをどのように扱うかに関わる重要な変更点です。

  8. Issue 2687とリビジョン 3c060add43fb: コミットメッセージで言及されている以前の関連するバグ修正です。ウェブ検索では、現在のGoのIssue 2687がHTTP/2の脆弱性に関するものであることが示されましたが、これはこのコミットが参照している古いIssue 2687とは異なります。このコミットは2012年のものであり、当時のIssue 2687は、インライン化や埋め込み型に関連する同様のコンパイラのバグを指していたと考えられます。リビジョン 3c060add43fb も同様に、当時のGoのソースコード管理システムにおける特定のコミットを指しており、現在のGitHubのコミット履歴では直接見つけることが難しい古いコミットである可能性が高いです。このコミットは、その古い修正とは異なるアプローチを取ることで、より堅牢な解決策を提供しています。

技術的詳細

このコミットの主要な目的は、Goコンパイラ gc が組み込み型(builtinpkg に属する型)の埋め込みフィールドを処理する際のシンボル解決の不整合を解消することです。この不整合は、特にインライン化が行われるような状況で、コンパイラが lookdot を使ってフィールドアクセスを解決しようとした際に、正しいシンボルを見つけられないという形で現れていました。

問題の根源は、Goの組み込み型がコンパイラ内部で特別な扱いを受けていたことにあります。これにより、構造体に埋め込まれた組み込み型のフィールドが、通常のユーザー定義型の埋め込みフィールドとは異なるシンボル解決パスを辿り、結果として lookdot が期待通りに機能しないケースが生じていました。

このコミットは、主に src/cmd/gc/dcl.csrc/cmd/gc/fmt.c の2つのファイルに修正を加えています。

  1. src/cmd/gc/dcl.c の変更: dcl.c は、Goコンパイラの宣言処理フェーズを担当する部分です。特に、embedded 関数は、構造体内の埋め込みフィールドの宣言を処理する際に呼び出されます。 このコミットでは、embedded 関数内に新しい条件分岐が追加されました。

    else if(s->pkg == builtinpkg && importpkg != nil)
        // The name of embedded builtins during imports belongs to importpkg.
        n = newname(pkglookup(name, importpkg));
    

    このコードは、埋め込みフィールドのシンボル sbuiltinpkg に属し、かつ現在のコンパイルコンテキストが他のパッケージからインポートされたコードを処理している場合(importpkg != nil)、その埋め込みフィールドの名前に対して pkglookup(name, importpkg) を使用して新しいシンボルを作成するように指示します。 pkglookup 関数は、指定された名前とパッケージに基づいてシンボルを検索または作成します。この変更により、組み込み型の埋め込みフィールドであっても、インポート元のパッケージに属する通常のパッケージローカルなシンボルとして扱われるようになります。これにより、コンパイラの型チェッカーは、これらのフィールドを lookdot を介して正しく識別できるようになり、インライン化が行われてもシンボル解決の失敗を防ぐことができます。

  2. src/cmd/gc/fmt.c の変更: fmt.c は、Goコンパイラの内部で抽象構文木(AST)ノードを文字列としてフォーマットする役割を担います。exprfmt 関数は、式ノードのフォーマットを担当します。 以前の exprfmt 関数には、構造体リテラル(OSTRUCTLIT)のフィールドをフォーマットする際に、埋め込み組み込み型に対する特別な処理が含まれていました。これは、シンボル解決の不完全さを補うための回避策でした。具体的には、l->n->left->type->embeddedtrue の場合に、フィールド名を非修飾で出力するための特殊なロジックが存在しました。 このコミットでは、dcl.c でのシンボル解決の改善により、この特別なフォーマット処理が不要になったため、関連するコードブロックが削除されました。これにより、埋め込み組み込み型であっても、通常のフィールドと同様にシンボルが正しく解決され、特別なフォーマット処理なしで一貫して扱えるようになりました。コードが簡素化され、コンパイラの内部ロジックがよりクリーンになりました。

これらの変更は、Goコンパイラの内部におけるシンボル管理と型解決の正確性を向上させ、特にインライン化と埋め込み型の相互作用によって引き起こされる複雑なバグを修正するものです。

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

このコミットにおける主要なコード変更は以下のファイルに集中しています。

  1. src/cmd/gc/dcl.c:

    • embedded 関数内に、組み込み型(builtinpkg)の埋め込みフィールドに対するシンボル解決ロジックが追加されました。
    --- a/src/cmd/gc/dcl.c
    +++ b/src/cmd/gc/dcl.c
    @@ -1013,6 +1013,9 @@ embedded(Sym *s)
     
     	if(exportname(name))
     		n = newname(lookup(name));
    +	else if(s->pkg == builtinpkg && importpkg != nil)
    +		// The name of embedded builtins during imports belongs to importpkg.
    +		n = newname(pkglookup(name, importpkg));
     	else
     		n = newname(pkglookup(name, s->pkg));
     	n = nod(ODCLFIELD, n, oldname(s));
    
  2. src/cmd/gc/fmt.c:

    • exprfmt 関数内で、埋め込み組み込み型に対する特別なフォーマット処理が削除されました。
    --- a/src/cmd/gc/fmt.c
    +++ b/src/cmd/gc/fmt.c
    @@ -1082,7 +1082,6 @@ exprfmt(Fmt *f, Node *n, int prec)
     {
     	int nprec;
     	NodeList *l;
    -	Type *t;
     
     	while(n && n->implicit && (n->op == OIND || n->op == OADDR))
     		n = n->left;
    @@ -1208,15 +1207,7 @@ exprfmt(Fmt *f, Node *n, int prec)
     			else
     				fmtprint(f, "(%T{\", n->type);
     			for(l=n->list; l; l=l->next) {
    -				// another special case: if n->left is an embedded field of builtin type,
    -				// it needs to be non-qualified.  Can't figure that out in %S, so do it here
    -				if(l->n->left->type->embedded) {
    -					t = l->n->left->type->type;
    -					if(t->sym == S)
    -						t = t->type;
    -					fmtprint(f, " %hhS:%N", t->sym, l->n->right);
    -				} else
    -					fmtprint(f, " %hhS:%N", l->n->left->sym, l->n->right);
    +				fmtprint(f, " %hhS:%N", l->n->left->sym, l->n->right);
     
     			if(l->next)
     				fmtstrcpy(f, ",");
    
  3. テストファイルの変更:

    • test/bugs/bug434.go が削除され、test/fixedbugs/issue3552.dir/one.gotest/fixedbugs/issue3552.dir/two.go にリネームされました。これは、古いバグテストケースを新しいIssue番号に合わせたものに置き換えたことを示します。
    • 新しいテストファイル test/fixedbugs/issue3552.go が追加されました。このファイルは、Issue #3552で報告された具体的なシナリオを再現し、このコミットによる修正が正しく機能することを確認するためのものです。
    • test/run.go から bugs/bug434.go のスキップリストエントリが削除されました。これは、bug434.go がもはやスキップされるべきではない、または新しいテストケースに置き換えられたことを意味します。

コアとなるコードの解説

src/cmd/gc/dcl.c の変更詳細

dcl.c はGoコンパイラの宣言処理に関連する部分であり、特に型や変数の宣言がどのように内部表現に変換されるかを扱います。embedded 関数は、構造体内に匿名フィールドとして埋め込まれた型を処理する際に呼び出されます。

変更前のコードでは、埋め込みフィールドのシンボルを決定する際に、その名前がエクスポートされているか(exportname(name))、または現在のパッケージに属しているか(pkglookup(name, s->pkg))に基づいて処理していました。しかし、組み込み型が埋め込まれた場合には、この既存のロジックでは不十分なケースがありました。

追加されたコードブロックは以下の通りです。

else if(s->pkg == builtinpkg && importpkg != nil)
    // The name of embedded builtins during imports belongs to importpkg.
    n = newname(pkglookup(name, importpkg));

この条件分岐は、以下の2つの条件が同時に満たされた場合に実行されます。

  1. s->pkg == builtinpkg: 処理中の埋め込みフィールドのシンボル s が、Goの組み込み型が属する特別なパッケージ builtinpkg に属している場合。
  2. importpkg != nil: 現在のコンパイルコンテキストが、他のパッケージからインポートされたコードを処理している場合。importpkg は、現在インポートされているパッケージの情報を保持するポインタです。これが nil でないということは、クロスパッケージのコンパイルが行われていることを意味します。

これらの条件が満たされると、newname(pkglookup(name, importpkg)) が呼び出されます。

  • pkglookup(name, importpkg): この関数は、指定された name(埋め込みフィールドの名前)と importpkg(インポート元のパッケージ)を使用して、そのパッケージ内でシンボルを検索または作成します。これにより、組み込み型の埋め込みフィールドであっても、インポート元のパッケージに属する通常のパッケージローカルなシンボルとして扱われるようになります。
  • newname(...): pkglookup によって取得されたシンボル情報に基づいて、新しいシンボルノード(Node)を作成します。このノードは、コンパイラの内部表現の一部となります。

この変更の核心は、組み込み型の埋め込みフィールドが、クロスパッケージのコンテキストで正しくシンボル解決されるようにすることです。これにより、コンパイラの型チェッカーが lookdot を使用してこれらのフィールドにアクセスする際に、正しいシンボル情報を見つけられるようになり、インライン化による問題が解消されます。

src/cmd/gc/fmt.c の変更詳細

fmt.c はGoコンパイラの内部で、抽象構文木(AST)ノードを人間が読める形式の文字列に変換する役割を担います。これはデバッグ出力やエラーメッセージの生成などに利用されます。exprfmt 関数は、様々な種類の式ノードをフォーマットする汎用的な関数です。

変更前の exprfmt 関数には、構造体リテラル(OSTRUCTLIT)のフィールドをフォーマットする際に、埋め込み組み込み型に対する特別な処理が含まれていました。これは、dcl.c でのシンボル解決が不完全であったために必要とされていた回避策です。

削除されたコードブロックは以下の通りです。

// another special case: if n->left is an embedded field of builtin type,
// it needs to be non-qualified.  Can't figure that out in %S, so do it here
if(l->n->left->type->embedded) {
    t = l->n->left->type->type;
    if(t->sym == S)
        t = t->type;
    fmtprint(f, " %hhS:%N", t->sym, l->n->right);
} else
    fmtprint(f, " %hhS:%N", l->n->left->sym, l->n->right);

このコードは、l->n->left が埋め込みフィールドであり、かつ組み込み型である場合に、フィールド名を非修飾で出力するための特殊なロジックでした。%hhS はシンボル名をフォーマットするための指定子です。この特殊な処理は、コンパイラが埋め込み組み込み型のシンボルを通常のフィールドシンボルとして扱えないために、表示上で調整する必要があったことを示しています。

今回のコミットでは、dcl.c でのシンボル解決の改善により、埋め込み組み込み型も通常のフィールドと同様にシンボルが正しく解決されるようになりました。その結果、この特別なフォーマット処理は不要となり、削除されました。

削除後、exprfmt 関数は常に以下の一般的なフォーマット処理を適用するようになりました。

fmtprint(f, " %hhS:%N", l->n->left->sym, l->n->right);

これは、埋め込み組み込み型であっても、通常のフィールドと同様にシンボルが正しく解決され、特別なフォーマット処理なしで一貫して扱えるようになったことを意味します。この変更は、コンパイラの内部ロジックを簡素化し、より一貫性のある処理フローを実現します。

関連リンク

  • Go Issue #3552: https://github.com/golang/go/issues/3552
    • このコミットが修正する具体的なバグ報告です。
  • Go CL 6866047: https://golang.org/cl/6866047
    • このコミットに対応するGo Gerrit Code Reviewのチェンジリストです。詳細な議論やレビューコメントが含まれている可能性があります。

参考にした情報源リンク

  • Go言語の公式ドキュメント: 埋め込み(Embedding)に関するセクション。
  • Goコンパイラのソースコード: 特に src/cmd/gc ディレクトリ内の dcl.cfmt.c
  • Go言語のコンパイラ設計に関する論文やブログ記事: Goコンパイラの内部構造、特に型チェック、シンボル解決、インライン化のメカニズムについて解説しているもの。
  • GoのIssueトラッカー: 過去のバグ報告や議論の履歴。
  • GoのGerrit Code Reviewシステム: 変更の提案とレビュープロセス。
  • Go言語の組み込み型に関する情報。
  • Go言語のシンボル解決とスコープに関する一般的な情報。