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

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

このコミットは、Goコンパイラ(gc)におけるインライン化のバグを修正するものです。具体的には、メソッドのレシーバがブランク識別子(_)である場合に、そのメソッドが正しくインライン化されない、またはインライン化された際に予期せぬ動作を引き起こす可能性があった問題に対処しています。また、インライン化処理中にソースコードの行番号情報が正しく保持されない問題も修正されています。

コミット

commit 81728cf06da55bfc66981e0df2414accc876bccc
Author: Russ Cox <rsc@golang.org>
Date:   Wed Jan 11 17:25:09 2012 -0500

    gc: fix inlining bug
    
    R=lvd
    CC=golang-dev
    https://golang.org/cl/5532077

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

https://github.com/golang/go/commit/81728cf06da55bfc66981e0df2414accc876bccc

元コミット内容

gc: fix inlining bug

R=lvd
CC=golang-dev
https://golang.org/cl/5532077

変更の背景

Go言語のコンパイラ(gc)は、プログラムの実行速度を向上させるために様々な最適化を行います。その一つが「インライン化(inlining)」です。インライン化とは、関数呼び出しのオーバーヘッドを削減するために、呼び出される関数の本体を呼び出し元に直接埋め込む最適化手法です。

このコミットが行われた背景には、Goコンパイラのインライン化処理に存在する特定のバグがありました。具体的には、以下の2つの問題が考えられます。

  1. ブランク識別子を持つレシーバのメソッドのインライン化問題: Go言語では、メソッドのレシーバに変数を割り当てたくない場合にブランク識別子 _ を使用できます。しかし、コンパイラがこのようなメソッドをインライン化しようとした際に、レシーバの処理に関するロジックが不完全であったため、正しくインライン化できない、またはインライン化されたコードが誤動作する可能性がありました。これは、コンパイラがブランク識別子を通常の変数名と同様に扱ってしまい、その結果として不要な処理やエラーを引き起こしていたためと考えられます。
  2. インライン化中の行番号情報の不整合: コンパイラがコードをインライン化する際、元のソースコードの行番号情報が失われたり、不正確になったりすることがあります。これはデバッグ時にスタックトレースが正しく表示されないなどの問題を引き起こします。このコミットでは、インライン化処理の途中で現在の行番号を一時的に保存し、処理後に復元することで、この不整合を解消しようとしています。

これらの問題は、Goプログラムのコンパイルと実行の安定性、およびデバッグの容易さに影響を与えるため、修正が必要とされました。test/fixedbugs/bug392.dir/one.gotwo.go に追加されたテストケースは、特にブランク識別子を持つレシーバのメソッドのインライン化に関する問題を再現し、修正を検証するために作成されたものです。

前提知識の解説

このコミットの変更内容を理解するためには、以下のGo言語およびコンパイラに関する基本的な知識が必要です。

  • Goコンパイラ (gc): Go言語の公式コンパイラです。ソースコードを機械語に変換し、実行可能なバイナリを生成します。gcは、最適化、型チェック、コード生成など、コンパイルの様々な段階を担当します。
  • インライン化 (Inlining): コンパイラ最適化の一種で、関数呼び出しのコスト(スタックフレームの作成、引数の渡し、戻り値の処理など)を削減するために、呼び出される関数のコードを呼び出し元の位置に直接展開する技術です。これにより、実行時のオーバーヘッドが減少し、プログラムのパフォーマンスが向上します。しかし、コードサイズが増加する可能性もあります。
  • メソッドとレシーバ: Go言語では、型に紐付けられた関数を「メソッド」と呼びます。メソッドは、そのメソッドが操作するインスタンスを指す「レシーバ」を引数として持ちます。レシーバは、func (r Type) MethodName(...) のように定義され、r がレシーバ変数です。
  • ブランク識別子 (_): Go言語の特殊な識別子で、値を破棄したい場合や、変数を宣言するがその値を使用しない場合に使用されます。例えば、_ = someFunction() のように、関数の戻り値を受け取るがその値を使わないことを明示できます。メソッドのレシーバとしても使用でき、func (_ *T) M() int のように、レシーバのインスタンス自体はメソッド内で使用しないことを示します。
  • Node (ASTノード): コンパイラ内部では、ソースコードは抽象構文木(Abstract Syntax Tree, AST)として表現されます。ASTは、プログラムの構造を木構造で表したもので、各ノード(Node)は、変数、関数呼び出し、演算子などのプログラム要素に対応します。コンパイラはASTを走査し、最適化やコード生成を行います。
  • NodeList: Nodeのリストを扱うためのデータ構造で、ASTの特定の要素(例えば、関数の初期化リストなど)を表現するために使用されます。
  • setlineno / lineno: Goコンパイラ内部で、現在の処理対象のソースコードの行番号を管理するための関数や変数です。デバッグ情報やエラーメッセージの生成に利用されます。
  • isblank: Goコンパイラ内部のヘルパー関数で、与えられた識別子がブランク識別子 (_) であるかどうかを判定します。

技術的詳細

このコミットは、主にsrc/cmd/gc/inl.cファイル内のinlnode関数とmkinlcall関数に修正を加えています。

inlnode関数の変更

inlnode関数は、ASTノードを走査し、インライン化可能な関数呼び出しを見つけて処理する役割を担っています。この関数に加えられた変更は、インライン化処理中にソースコードの行番号情報が正しく保持されるようにすることです。

  • int lno; の追加: inlnode関数の冒頭に、現在の行番号を一時的に保存するためのローカル変数lnoが追加されました。
  • lno = setlineno(n); の追加: インライン化処理を開始する前に、現在のノードnに対応する行番号を取得し、lnoに保存しています。setlineno(n)は、nの行番号を現在のグローバルな行番号変数linenoに設定し、その古い値を返す関数です。
  • lineno = lno; の追加: inlnode関数の処理が終了する直前に、保存しておいたlnoの値をグローバルな行番号変数linenoに戻しています。これにより、インライン化処理中にlinenoが変更されても、処理が完了した後に元の行番号が復元され、デバッグ情報などの整合性が保たれます。

この変更は、インライン化されたコードがデバッガでステップ実行されたり、パニック発生時にスタックトレースが表示されたりする際に、より正確なソースコードの行番号が報告されるようにするために重要です。

mkinlcall関数の変更

mkinlcall関数は、インライン化される関数呼び出しのASTノードを構築する際に使用されます。この関数に加えられた変更は、メソッドのレシーバがブランク識別子である場合のインライン化のバグを修正するものです。

元のコードでは、メソッドのレシーバを処理する際に、レシーバ名がnilでないこと、およびinlvar(インライン化された変数)が設定されていないことをチェックしていました。しかし、ブランク識別子 _nnamenilではないものの、特別な意味を持つため、通常の変数名とは異なる扱いが必要です。

修正後のコードでは、以下の条件が追加されています。

  • !isblank(t->nname) の追加: t->nname(レシーバの型名)がブランク識別子であるかどうかをisblank関数でチェックしています。
    • if(t != T && t->nname != N && !isblank(t->nname) && !t->nname->inlvar)
    • if(t != T && t->nname != N && !isblank(t->nname)) (2箇所)

この変更により、mkinlcall関数は、レシーバがブランク識別子である場合に、そのレシーバを通常の変数として扱わないようになります。これにより、ブランク識別子を持つメソッドがインライン化される際に、コンパイラが誤ったコードを生成したり、内部エラーを引き起こしたりするのを防ぎます。ブランク識別子は、その値が使用されないことを意味するため、インライン化の際に特別な変数割り当てやチェックを行う必要がない、あるいは行うべきではないケースが存在します。この修正は、その特殊なケースを正しくハンドリングするためのものです。

テストケースの追加

test/fixedbugs/bug392.dir/one.gotest/fixedbugs/bug392.dir/two.go には、このバグを再現し、修正を検証するための新しいテストケースが追加されています。

  • one.go には、ブランク識別子をレシーバとするメソッド M() と、そのメソッドを呼び出す MM() が追加されています。
    func (_ *T) M() int { return 1 }
    func (t *T) MM() int { return t.M() }
    
  • two.go には、one.go で定義された M()MM() メソッドを呼び出すコードが追加されています。
    var t *one.T
    t.M()
    t.MM()
    

これらのテストケースは、コンパイラが t.M()t.MM() のような呼び出しをインライン化する際に、ブランク識別子を持つレシーバが正しく処理されることを確認するために使用されます。

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

diff --git a/src/cmd/gc/inl.c b/src/cmd/gc/inl.c
index 8830f6bb12..137d913711 100644
--- a/src/cmd/gc/inl.c
+++ b/src/cmd/gc/inl.c
@@ -290,12 +290,13 @@ inlnode(Node **np)
 {
  	Node *n;
  	NodeList *l;
+	int lno;
  
  	if(*np == nil)
  		return;
  
  	n = *np;
-
+
  	switch(n->op) {
  	case ODEFER:
  	case OPROC:
@@ -312,6 +313,8 @@ inlnode(Node **np)
  		return;
  	}
  
+	lno = setlineno(n);
+
  	inlnodelist(n->ninit);
  	for(l=n->ninit; l; l=l->next)
  	\tif(l->n->op == OINLCALL)
@@ -431,6 +434,8 @@ inlnode(Node **np)
  
  	\tbreak;
  	}\n+\t
+\tlineno = lno;
 }\n      \n     // if *np is a call, and fn is a function with an inlinable body, substitute *np with an OINLCALL.\n    @@ -495,20 +500,19 @@ mkinlcall(Node **np, Node *fn)\n     \tas = N;\n     \tif(fn->type->thistuple) {\n     \t\tt = getthisx(fn->type)->type;\n-\n-\t\t\tif(t != T && t->nname != N && !t->nname->inlvar)\n    +\t\t\tif(t != T && t->nname != N && !isblank(t->nname) && !t->nname->inlvar)\n     \t\t\tfatal(\"missing inlvar for %N\\n\", t->nname);\n      \n     \t\tif(n->left->op == ODOTMETH) {\n     \t\t\tif (!n->left->left)\n     \t\t\t\tfatal(\"method call without receiver: %+N\", n);\n-\t\t\tif(t != T && t->nname)\n    +\t\t\tif(t != T && t->nname != N && !isblank(t->nname))\n     \t\t\t\tas = nod(OAS, t->nname->inlvar, n->left->left);\n     \t\t\t// else if !ONAME add to init anyway?\n     \t\t} else {  // non-method call to method\n     \t\t\tif (!n->list)\n     \t\t\t\tfatal(\"non-method call to method without first arg: %+N\", n);\n-\t\t\tif(t != T && t->nname)\n    +\t\t\tif(t != T && t->nname != N && !isblank(t->nname))\n     \t\t\t\tas = nod(OAS, t->nname->inlvar, n->list->n);\n     \t\t}\n      

コアとなるコードの解説

src/cmd/gc/inl.c の変更点

このファイルはGoコンパイラのインライン化処理を担当する部分です。

  1. inlnode 関数における行番号の保存と復元:

    • 追加された int lno; は、インライン化処理に入る前の現在のソースコード行番号を一時的に保持するための変数です。
    • lno = setlineno(n); は、現在のASTノード n に関連付けられた行番号をコンパイラのグローバルな行番号管理変数 lineno に設定し、同時にその変更前の lineno の値を lno に保存します。これにより、インライン化処理中に lineno が更新されても、元のコンテキストの行番号を失わないようにします。
    • lineno = lno; は、inlnode 関数でのインライン化処理が完了した後、保存しておいた lno の値(つまり、インライン化処理に入る前の行番号)を lineno に戻します。この操作により、インライン化によってコードが展開された後も、デバッグ情報やエラー報告において正しいソースコードの行番号が参照されるようになります。これは、特にスタックトレースの正確性を保証するために重要です。
  2. mkinlcall 関数におけるブランク識別子レシーバのハンドリング:

    • mkinlcall 関数は、インライン化される関数呼び出しのASTノードを構築する際に、レシーバの処理を行います。
    • 変更前は、レシーバの型名 (t->nname) が nil でなく、かつ inlvar が設定されていない場合に特定の処理を行っていました。しかし、Go言語のブランク識別子 (_) は、変数名としては有効ですが、その値が使用されないことを意味するため、通常の変数とは異なる扱いが必要です。
    • 追加された !isblank(t->nname) という条件は、t->nname がブランク識別子でないことを確認します。
      • if(t != T && t->nname != N && !isblank(t->nname) && !t->nname->inlvar): この行は、レシーバが通常の変数であり、かつ inlvar が設定されていない場合にのみ、fatal エラー(コンパイラ内部エラー)を発生させるように変更されました。これにより、ブランク識別子を持つレシーバに対して誤って inlvar のチェックを行わないようになります。
      • if(t != T && t->nname != N && !isblank(t->nname)): この行は、メソッド呼び出しのレシーバが通常の変数である場合にのみ、レシーバの値を inlvar に割り当てる OAS (代入) ノードを生成するように変更されました。ブランク識別子の場合は、レシーバの値が不要であるため、この代入処理をスキップします。

これらの変更により、Goコンパイラは、ブランク識別子をレシーバとするメソッドのインライン化をより正確に処理できるようになり、コンパイル時のエラーや実行時の予期せぬ動作を防ぎます。

test/fixedbugs/bug392.dir/one.go および two.go の変更点

これらのファイルは、Goコンパイラのバグ修正を検証するためのテストケースです。

  • one.go:
    • func (_ *T) M() int { return 1 }: ブランク識別子 _ をレシーバとするメソッド M を定義しています。これは、レシーバの値自体はメソッド内で使用しないことを示します。
    • func (t *T) MM() int { return t.M() }: M メソッドを呼び出す別のメソッド MM を定義しています。
  • two.go:
    • var t *one.T: one.T 型のポインタ変数 t を宣言しています。
    • t.M(): ブランク識別子レシーバのメソッド M を呼び出しています。
    • t.MM(): M を呼び出す MM メソッドを呼び出しています。

これらのテストケースは、コンパイラが t.M()t.MM() のような呼び出しをインライン化する際に、ブランク識別子を持つレシーバが正しく処理され、コンパイルが成功し、期待通りの動作をすることを確認するために使用されます。

関連リンク

参考にした情報源リンク

このコミットは、Goコンパイラ(gc)におけるインライン化のバグを修正するものです。具体的には、メソッドのレシーバがブランク識別子(_)である場合に、そのメソッドが正しくインライン化されない、またはインライン化された際に予期せぬ動作を引き起こす可能性があった問題に対処しています。また、インライン化処理中にソースコードの行番号情報が正しく保持されない問題も修正されています。

コミット

commit 81728cf06da55bfc66981e0df2414accc876bccc
Author: Russ Cox <rsc@golang.org>
Date:   Wed Jan 11 17:25:09 2012 -0500

    gc: fix inlining bug
    
    R=lvd
    CC=golang-dev
    https://golang.org/cl/5532077

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

https://github.com/golang/go/commit/81728cf06da55bfc66981e0df2414accc876bccc

元コミット内容

gc: fix inlining bug

R=lvd
CC=golang-dev
https://golang.org/cl/5532077

変更の背景

Go言語のコンパイラ(gc)は、プログラムの実行速度を向上させるために様々な最適化を行います。その一つが「インライン化(inlining)」です。インライン化とは、関数呼び出しのオーバーヘッドを削減するために、呼び出される関数の本体を呼び出し元に直接埋め込む最適化手法です。

このコミットが行われた背景には、Goコンパイラのインライン化処理に存在する特定のバグがありました。具体的には、以下の2つの問題が考えられます。

  1. ブランク識別子を持つレシーバのメソッドのインライン化問題: Go言語では、メソッドのレシーバに変数を割り当てたくない場合にブランク識別子 _ を使用できます。しかし、コンパイラがこのようなメソッドをインライン化しようとした際に、レシーバの処理に関するロジックが不完全であったため、正しくインライン化できない、またはインライン化されたコードが誤動作する可能性がありました。これは、コンパイラがブランク識別子を通常の変数名と同様に扱ってしまい、その結果として不要な処理やエラーを引き起こしていたためと考えられます。
  2. インライン化中の行番号情報の不整合: コンパイラがコードをインライン化する際、元のソースコードの行番号情報が失われたり、不正確になったりすることがあります。これはデバッグ時にスタックトレースが正しく表示されないなどの問題を引き起こします。このコミットでは、インライン化処理の途中で現在の行番号を一時的に保存し、処理後に復元することで、この不整合を解消しようとしています。

これらの問題は、Goプログラムのコンパイルと実行の安定性、およびデバッグの容易さに影響を与えるため、修正が必要とされました。test/fixedbugs/bug392.dir/one.gotwo.go に追加されたテストケースは、特にブランク識別子を持つレシーバのメソッドのインライン化に関する問題を再現し、修正を検証するために作成されたものです。

前提知識の解説

このコミットの変更内容を理解するためには、以下のGo言語およびコンパイラに関する基本的な知識が必要です。

  • Goコンパイラ (gc): Go言語の公式コンパイラです。ソースコードを機械語に変換し、実行可能なバイナリを生成します。gcは、最適化、型チェック、コード生成など、コンパイルの様々な段階を担当します。
  • インライン化 (Inlining): コンパイラ最適化の一種で、関数呼び出しのコスト(スタックフレームの作成、引数の渡し、戻り値の処理など)を削減するために、呼び出される関数のコードを呼び出し元の位置に直接展開する技術です。これにより、実行時のオーバーヘッドが減少し、プログラムのパフォーマンスが向上します。しかし、コードサイズが増加する可能性もあります。
  • メソッドとレシーバ: Go言語では、型に紐付けられた関数を「メソッド」と呼びます。メソッドは、そのメソッドが操作するインスタンスを指す「レシーバ」を引数として持ちます。レシーバは、func (r Type) MethodName(...) のように定義され、r がレシーバ変数です。
  • ブランク識別子 (_): Go言語の特殊な識別子で、値を破棄したい場合や、変数を宣言するがその値を使用しない場合に使用されます。例えば、_ = someFunction() のように、関数の戻り値を受け取るがその値を使わないことを明示できます。メソッドのレシーバとしても使用でき、func (_ *T) M() int のように、レシーバのインスタンス自体はメソッド内で使用しないことを示します。
  • Node (ASTノード): コンパイラ内部では、ソースコードは抽象構文木(Abstract Syntax Tree, AST)として表現されます。ASTは、プログラムの構造を木構造で表したもので、各ノード(Node)は、変数、関数呼び出し、演算子などのプログラム要素に対応します。コンパイラはASTを走査し、最適化やコード生成を行います。
  • NodeList: Nodeのリストを扱うためのデータ構造で、ASTの特定の要素(例えば、関数の初期化リストなど)を表現するために使用されます。
  • setlineno / lineno: Goコンパイラ内部で、現在の処理対象のソースコードの行番号を管理するための関数や変数です。デバッグ情報やエラーメッセージの生成に利用されます。
  • isblank: Goコンパイラ内部のヘルパー関数で、与えられた識別子がブランク識別子 (_) であるかどうかを判定します。

技術的詳細

このコミットは、主にsrc/cmd/gc/inl.cファイル内のinlnode関数とmkinlcall関数に修正を加えています。

inlnode関数の変更

inlnode関数は、ASTノードを走査し、インライン化可能な関数呼び出しを見つけて処理する役割を担っています。この関数に加えられた変更は、インライン化処理中にソースコードの行番号情報が正しく保持されるようにすることです。

  • int lno; の追加: inlnode関数の冒頭に、現在の行番号を一時的に保存するためのローカル変数lnoが追加されました。
  • lno = setlineno(n); の追加: インライン化処理を開始する前に、現在のノードnに対応する行番号を取得し、lnoに保存しています。setlineno(n)は、nの行番号を現在のグローバルな行番号変数linenoに設定し、その古い値を返す関数です。
  • lineno = lno; の追加: inlnode関数の処理が終了する直前に、保存しておいたlnoの値をグローバルな行番号変数linenoに戻しています。これにより、インライン化処理中にlinenoが変更されても、処理が完了した後に元の行番号が復元され、デバッグ情報などの整合性が保たれます。

この変更は、インライン化されたコードがデバッガでステップ実行されたり、パニック発生時にスタックトレースが表示されたりする際に、より正確なソースコードの行番号が報告されるようにするために重要です。

mkinlcall関数の変更

mkinlcall関数は、インライン化される関数呼び出しのASTノードを構築する際に使用されます。この関数に加えられた変更は、メソッドのレシーバがブランク識別子である場合のインライン化のバグを修正するものです。

元のコードでは、メソッドのレシーバを処理する際に、レシーバ名がnilでないこと、およびinlvar(インライン化された変数)が設定されていないことをチェックしていました。しかし、ブランク識別子 _nnamenilではないものの、特別な意味を持つため、通常の変数名とは異なる扱いが必要です。

修正後のコードでは、以下の条件が追加されています。

  • !isblank(t->nname) の追加: t->nname(レシーバの型名)がブランク識別子であるかどうかをisblank関数でチェックしています。
    • if(t != T && t->nname != N && !isblank(t->nname) && !t->nname->inlvar)
    • if(t != T && t->nname != N && !isblank(t->nname)) (2箇所)

この変更により、mkinlcall関数は、レシーバがブランク識別子である場合に、そのレシーバを通常の変数として扱わないようになります。これにより、ブランク識別子を持つメソッドがインライン化される際に、コンパイラが誤ったコードを生成したり、内部エラーを引き起こしたりするのを防ぎます。ブランク識別子は、その値が使用されないことを意味するため、インライン化の際に特別な変数割り当てやチェックを行う必要がない、あるいは行うべきではないケースが存在します。この修正は、その特殊なケースを正しくハンドリングするためのものです。

テストケースの追加

test/fixedbugs/bug392.dir/one.gotest/fixedbugs/bug392.dir/two.go には、このバグを再現し、修正を検証するための新しいテストケースが追加されています。

  • one.go には、ブランク識別子をレシーバとするメソッド M() と、そのメソッドを呼び出す MM() が追加されています。
    func (_ *T) M() int { return 1 }
    func (t *T) MM() int { return t.M() }
    
  • two.go には、one.go で定義された M()MM() メソッドを呼び出すコードが追加されています。
    var t *one.T
    t.M()
    t.MM()
    

これらのテストケースは、コンパイラが t.M()t.MM() のような呼び出しをインライン化する際に、ブランク識別子を持つレシーバが正しく処理されることを確認するために使用されます。

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

diff --git a/src/cmd/gc/inl.c b/src/cmd/gc/inl.c
index 8830f6bb12..137d913711 100644
--- a/src/cmd/gc/inl.c
+++ b/src/cmd/gc/inl.c
@@ -290,12 +290,13 @@ inlnode(Node **np)
 {
  	Node *n;
  	NodeList *l;
+	int lno;
  
  	if(*np == nil)
  		return;
  
  	n = *np;
-
+
  	switch(n->op) {
  	case ODEFER:
  	case OPROC:
@@ -312,6 +313,8 @@ inlnode(Node **np)
  		return;
  	}
  
+	lno = setlineno(n);
+
  	inlnodelist(n->ninit);
  	for(l=n->ninit; l; l=l->next)
  	\tif(l->n->op == OINLCALL)
@@ -431,6 +434,8 @@ inlnode(Node **np)
  
  	\tbreak;
  	}\n+\t
+\tlineno = lno;
 }\n      \n     // if *np is a call, and fn is a function with an inlinable body, substitute *np with an OINLCALL.\n    @@ -495,20 +500,19 @@ mkinlcall(Node **np, Node *fn)\n     \tas = N;\n     \tif(fn->type->thistuple) {\n     \t\tt = getthisx(fn->type)->type;\n-\n-\t\t\tif(t != T && t->nname != N && !t->nname->inlvar)\n    +\t\t\tif(t != T && t->nname != N && !isblank(t->nname) && !t->nname->inlvar)\n     \t\t\tfatal(\"missing inlvar for %N\\n\", t->nname);\n      \n     \t\tif(n->left->op == ODOTMETH) {\n     \t\t\tif (!n->left->left)\n     \t\t\t\tfatal(\"method call without receiver: %+N\", n);\n-\t\t\tif(t != T && t->nname)\n    +\t\t\tif(t != T && t->nname != N && !isblank(t->nname))\n     \t\t\t\tas = nod(OAS, t->nname->inlvar, n->left->left);\n     \t\t\t// else if !ONAME add to init anyway?\n     \t\t} else {  // non-method call to method\n     \t\t\tif (!n->list)\n     \t\t\t\tfatal(\"non-method call to method without first arg: %+N\", n);\n-\t\t\tif(t != T && t->nname)\n    +\t\t\tif(t != T && t->nname != N && !isblank(t->nname))\n     \t\t\t\tas = nod(OAS, t->nname->inlvar, n->list->n);\n     \t\t}\n      

コアとなるコードの解説

src/cmd/gc/inl.c の変更点

このファイルはGoコンパイラのインライン化処理を担当する部分です。

  1. inlnode 関数における行番号の保存と復元:

    • 追加された int lno; は、インライン化処理に入る前の現在のソースコード行番号を一時的に保持するための変数です。
    • lno = setlineno(n); は、現在のASTノード n に関連付けられた行番号をコンパイラのグローバルな行番号管理変数 lineno に設定し、同時にその変更前の lineno の値を lno に保存します。これにより、インライン化処理中に lineno が更新されても、元のコンテキストの行番号を失わないようにします。
    • lineno = lno; は、inlnode 関数でのインライン化処理が完了した後、保存しておいた lno の値(つまり、インライン化処理に入る前の行番号)を lineno に戻します。この操作により、インライン化によってコードが展開された後も、デバッグ情報やエラー報告において正しいソースコードの行番号が参照されるようになります。これは、特にスタックトレースの正確性を保証するために重要です。
  2. mkinlcall 関数におけるブランク識別子レシーバのハンドリング:

    • mkinlcall 関数は、インライン化される関数呼び出しのASTノードを構築する際に、レシーバの処理を行います。
    • 変更前は、レシーバの型名 (t->nname) が nil でなく、かつ inlvar が設定されていない場合に特定の処理を行っていました。しかし、Go言語のブランク識別子 (_) は、変数名としては有効ですが、その値が使用されないことを意味するため、通常の変数とは異なる扱いが必要です。
    • 追加された !isblank(t->nname) という条件は、t->nname がブランク識別子でないことを確認します。
      • if(t != T && t->nname != N && !isblank(t->nname) && !t->nname->inlvar): この行は、レシーバが通常の変数であり、かつ inlvar が設定されていない場合にのみ、fatal エラー(コンパイラ内部エラー)を発生させるように変更されました。これにより、ブランク識別子を持つレシーバに対して誤って inlvar のチェックを行わないようになります。
      • if(t != T && t->nname != N && !isblank(t->nname)): この行は、メソッド呼び出しのレシーバが通常の変数である場合にのみ、レシーバの値を inlvar に割り当てる OAS (代入) ノードを生成するように変更されました。ブランク識別子の場合は、レシーバの値が不要であるため、この代入処理をスキップします。

これらの変更により、Goコンパイラは、ブランク識別子をレシーバとするメソッドのインライン化をより正確に処理できるようになり、コンパイル時のエラーや実行時の予期せぬ動作を防ぎます。

test/fixedbugs/bug392.dir/one.go および two.go の変更点

これらのファイルは、Goコンパイラのバグ修正を検証するためのテストケースです。

  • one.go:
    • func (_ *T) M() int { return 1 }: ブランク識別子 _ をレシーバとするメソッド M を定義しています。これは、レシーバの値自体はメソッド内で使用しないことを示します。
    • func (t *T) MM() int { return t.M() }: M メソッドを呼び出す別のメソッド MM を定義しています。
  • two.go:
    • var t *one.T: one.T 型のポインタ変数 t を宣言しています。
    • t.M(): ブランク識別子レシーバのメソッド M を呼び出しています。
    • t.MM(): M を呼び出す MM メソッドを呼び出しています。

これらのテストケースは、コンパイラが t.M()t.MM() のような呼び出しをインライン化する際に、ブランク識別子を持つレシーバが正しく処理され、コンパイルが成功し、期待通りの動作をすることを確認するために使用されます。

関連リンク

参考にした情報源リンク