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

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

このコミットは、Go言語の実験的なSSA (Static Single Assignment) パッケージ (exp/ssa) において、関数(メソッドを含む)の参照を出力する際の表示形式を改善するものです。具体的には、同じパッケージ内の関数を参照する場合に、パッケージ名を省略して出力するように変更することで、SSA表現の可読性を向上させています。

コミット

commit be5deb93fb1af69a01acef214274a0708d711221
Author: Alan Donovan <adonovan@google.com>
Date:   Tue Feb 12 16:13:14 2013 -0500

    exp/ssa: omit Function's package name when printing intra-package references.
    
    R=iant
    CC=golang-dev
    https://golang.org/cl/7307105

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

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

元コミット内容

exp/ssa: omit Function's package name when printing intra-package references.

(exp/ssa: パッケージ内参照を出力する際、関数のパッケージ名を省略する。)

変更の背景

Go言語のコンパイラやツールチェーンでは、プログラムの解析や最適化のために中間表現(IR)が用いられます。SSA (Static Single Assignment) はその強力な中間表現の一つであり、Go言語にも実験的なSSAパッケージ (exp/ssa) が存在しました。このパッケージは、GoプログラムをSSA形式に変換し、その構造をダンプ(出力)する機能を提供します。

しかし、従来のSSAダンプでは、同じパッケージ内の関数を参照する場合でも、常にパッケージ名がプレフィックスとして付与されていました(例: main.myFunction)。これは冗長であり、特に大規模なプログラムや複雑なSSAグラフをデバッグ・解析する際に、出力の可読性を著しく低下させていました。

このコミットの目的は、この冗長性を排除し、Go言語の通常のコード記述におけるパッケージ修飾の慣習(同じパッケージ内の参照ではパッケージ名を省略する)に合わせることで、SSAダンプの出力をより自然で理解しやすいものにすることにあります。これにより、開発者がSSA表現をより効率的に利用できるようになります。

前提知識の解説

1. SSA (Static Single Assignment)

SSAは、コンパイラの中間表現の一種で、プログラム内の各変数に一度だけ値が代入されるように変換する手法です。これにより、データフロー解析や最適化が大幅に簡素化されます。例えば、x = a + b; x = c + d; というコードは、SSAでは x1 = a + b; x2 = c + d; のように変換され、各代入がユニークな変数に紐付けられます。Go言語のコンパイラも、内部的にSSA形式を利用して最適化を行っています。exp/ssa パッケージは、このSSA形式をGoプログラムから生成し、操作するための実験的なAPIを提供していました。

2. Go言語のパッケージとインポートパス

Go言語では、コードは「パッケージ」という単位で整理されます。パッケージは、関連する機能の集合であり、名前空間を提供します。他のパッケージの公開された識別子(関数、変数、型など)を利用するには、そのパッケージをインポートし、パッケージ名.識別子 の形式で参照します。しかし、同じパッケージ内の識別子を参照する場合は、パッケージ名を省略して直接識別子名を使用します。このコミットは、SSAの出力においてもこの慣習を反映させようとしています。

3. exp/ssa パッケージ

exp/ssa は、Go言語の標準ライブラリの一部として提供されていた実験的なパッケージです。Goプログラムの抽象構文木 (AST) からSSA形式のグラフを構築する機能を提供していました。これは、コンパイラの開発者や、Goプログラムの静的解析ツールを構築する開発者にとって有用なツールでした。このパッケージは後にGoコンパイラ本体に統合され、直接利用されることは少なくなりましたが、その概念はGoのコンパイラ設計において非常に重要です。

4. FullName()Name() メソッド

GoのSSAパッケージにおける Function 型には、関数名を文字列として取得するためのメソッドがいくつか存在します。

  • Name(): 関数の短い名前(例: myFunction)。
  • FullName(): 関数の完全修飾名。これには、パッケージ名やレシーバの型情報が含まれる場合があります(例: main.myFunction(main.MyType).Method)。

このコミットは、FullName() の動作を調整し、特定のコンテキスト(同じパッケージ内からの参照)でパッケージ名を省略するように変更しています。

技術的詳細

このコミットの核心は、Function 型に新しいメソッド fullName(from *Package) string を導入し、既存の FullName() メソッドと relName 関数がこの新しいメソッドを利用するように変更した点にあります。

  1. Function.fullName(from *Package) string の導入:

    • この新しいメソッドは、from という *Package 型の引数を受け取ります。これは、現在参照を行っているコンテキストのパッケージを示します。
    • メソッドの内部では、f.Pkg (現在の関数のパッケージ) と from パッケージを比較します。
    • もし f.Pkg == from であれば、つまり参照元と参照先の関数が同じパッケージに属している場合、pkgQual という文字列変数を空 ("") に設定します。
    • そうでなければ(異なるパッケージからの参照の場合)、pkgQualf.Pkg.ImportPath + "." を設定し、パッケージ名を修飾子として含めます。
    • 最終的な関数名の文字列は、この pkgQual を利用して構築されます。これにより、同じパッケージ内の参照ではパッケージ名が省略され、異なるパッケージからの参照ではパッケージ名が明示されるようになります。
  2. Function.FullName() の変更:

    • 既存の FullName() メソッドは、引数なしで呼び出されるため、常にパッケージ名を修飾する動作を維持する必要がありました。そのため、FullName() は単に f.fullName(nil) を呼び出すように変更されました。nil を渡すことで、fullName メソッド内で f.Pkg == nil または f.Pkg != from の条件が常に真となり、パッケージ名が常に含まれるようになります。
  3. relName 関数の変更:

    • src/pkg/exp/ssa/print.go にある relName 関数は、SSAグラフを出力する際に、値(Value)の名前を、特定の命令(Instruction)からの相対的な形で取得するために使用されます。
    • この関数内で、*Function 型の値の名前を取得する際に、従来の v.FullName() を直接呼び出すのではなく、新しく導入された v.fullName(i.Block().Func.Pkg) を呼び出すように変更されました。
    • i.Block().Func.Pkg は、現在処理している命令が属するブロック、そしてそのブロックが属する関数のパッケージを指します。これを from 引数として fullName に渡すことで、SSAダンプの出力時に、参照元と参照先の関数が同じパッケージに属していればパッケージ名が省略されるという、このコミットの主要な目的が達成されます。
  4. ssa.go のコメント更新:

    • Function 構造体の Pkg フィールドに関するコメントが、「Goソース関数に対する囲みパッケージ。それ以外の場合はnil」と更新され、Pkg フィールドの役割がより明確になりました。

これらの変更により、SSAダンプの出力は、Go言語の通常のコードの読み方により近くなり、視覚的なノイズが減少し、デバッグや解析が容易になります。

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

src/pkg/exp/ssa/func.go

--- a/src/pkg/exp/ssa/func.go
+++ b/src/pkg/exp/ssa/func.go
@@ -344,6 +344,11 @@ func (f *Function) emit(instr Instruction) Value {
 //      "func@5.32"                 // an anonymous function
 //
 func (f *Function) FullName() string {
+	return f.fullName(nil)
+}
+
+// Like FullName, but if from==f.Pkg, suppress package qualification.
+func (f *Function) fullName(from *Package) string {
 	// Anonymous?
 	if f.Enclosing != nil {
 		return f.Name_
@@ -353,11 +358,20 @@ func (f *Function) FullName() string {
 
 	// Synthetic?
 	if f.Pkg == nil {
+		var recvType types.Type
 		if recv != nil {
-			// TODO(adonovan): print type package-qualified, if NamedType.
-			return fmt.Sprintf("(%s).%s", recv.Type, f.Name_) // bridge method
+			recvType = recv.Type // bridge method
+		} else {
+			recvType = f.Params[0].Type() // interface method thunk
 		}
-		return fmt.Sprintf("(%s).%s", f.Params[0].Type(), f.Name_) // interface method thunk
+		// TODO(adonovan): print type package-qualified, if NamedType.
+		return fmt.Sprintf("(%s).%s", recvType, f.Name_)
+	}
+
+	// "pkg." prefix for cross-package references only.
+	var pkgQual string
+	if from != f.Pkg {
+		pkgQual = f.Pkg.ImportPath + "."
 	}
 
 	// Declared method?
@@ -366,11 +380,11 @@ func (f *Function) FullName() string {
 		if isPointer(recv.Type) {
 			star = "*"
 		}
-		return fmt.Sprintf("(%s%s.%s).%s", star, f.Pkg.ImportPath, deref(recv.Type), f.Name_)
+		return fmt.Sprintf("(%s%s%s).%s", star, pkgQual, deref(recv.Type), f.Name_)
 	}
 
 	// Package-level function.
-	return fmt.Sprintf("%s.%s", f.Pkg.ImportPath, f.Name_)
+	return pkgQual + f.Name_
 }
 
 // DumpTo prints to w a human readable "disassembly" of the SSA code of

src/pkg/exp/ssa/print.go

--- a/src/pkg/exp/ssa/print.go
+++ b/src/pkg/exp/ssa/print.go
@@ -20,9 +20,9 @@ func (id Id) String() string {\n }\n \n // relName returns the name of v relative to i.\n-// In most cases, this is identical to v.Name(), but for cross-package\n-// references to Functions (including methods) and Globals, the\n-// package-qualified FullName is used instead.\n+// In most cases, this is identical to v.Name(), but for references to\n+// Functions (including methods) and Globals, the FullName is used\n+// instead, explicitly package-qualified for cross-package references.\n //\n func relName(v Value, i Instruction) string {\n 	switch v := v.(type) {\n@@ -32,10 +32,7 @@ func relName(v Value, i Instruction) string {\n 		}\n 		return v.FullName()\n 	case *Function:\n-\t\tif v.Pkg == nil || v.Pkg == i.Block().Func.Pkg {\n-\t\t\treturn v.Name()\n-\t\t}\n-\t\treturn v.FullName()\n+\t\treturn v.fullName(i.Block().Func.Pkg)\n 	}\n 	return v.Name()\n }\

src/pkg/exp/ssa/ssa.go

--- a/src/pkg/exp/ssa/ssa.go
+++ b/src/pkg/exp/ssa/ssa.go
@@ -204,7 +204,7 @@ type Function struct {\n 
 	Pos       token.Pos // location of the definition
 	Enclosing *Function // enclosing function if anon; nil if global
-	Pkg       *Package  // enclosing package; nil for some synthetic methods
+	Pkg       *Package  // enclosing package for Go source functions; otherwise nil
 	Prog      *Program  // enclosing program
 	Params    []*Parameter
 	FreeVars  []*Capture // free variables whose values must be supplied by closure

コアとなるコードの解説

src/pkg/exp/ssa/func.go の変更

  • FullName() メソッドの変更:

    • 以前は直接関数名の文字列を構築していましたが、新しい fullName メソッドを呼び出すように変更されました。return f.fullName(nil) とすることで、FullName() が呼び出された際には常にパッケージ名が修飾されるように動作が維持されます。これは、FullName() が「完全な名前」を返すという意図に合致します。
  • fullName(from *Package) string メソッドの追加:

    • このメソッドが今回の変更の肝です。from パッケージが現在の関数のパッケージ (f.Pkg) と同じであれば、pkgQual 変数を空文字列に設定します。これにより、後続の fmt.Sprintf でパッケージ名が挿入されなくなります。
    • from パッケージが異なるか、f.Pkgnil の場合は、f.Pkg.ImportPath + "."pkgQual に設定し、パッケージ名を明示的に含めます。
    • メソッドやパッケージレベルの関数の名前を構築する際に、この pkgQual を利用することで、条件付きでパッケージ名を付与するロジックが実現されています。

src/pkg/exp/ssa/print.go の変更

  • relName 関数の変更:
    • relName 関数は、SSAの出力において、ある命令 (i) から見た別の値 (v) の名前を生成する役割を担っています。
    • 特に *Function 型の値 (v) の名前を生成する部分が変更されました。
    • 変更前は、v.Pkg == nil || v.Pkg == i.Block().Func.Pkg という条件でパッケージ名を省略するかどうかを判断し、v.Name() または v.FullName() を呼び分けていました。
    • 変更後は、この複雑な条件分岐を削除し、新しく追加された v.fullName(i.Block().Func.Pkg) を直接呼び出すようになりました。
    • i.Block().Func.Pkg は、現在処理中の命令が属する関数のパッケージを意味します。これを fullName メソッドの from 引数として渡すことで、SSAダンプの出力時に、参照元と参照先の関数が同じパッケージに属していればパッケージ名が自動的に省略されるようになります。これにより、コードが簡潔になり、意図がより明確になりました。

src/pkg/exp/ssa/ssa.go の変更

  • Function 構造体の Pkg フィールドのコメントが更新され、Pkg がGoソース関数に対する囲みパッケージであり、それ以外の場合は nil であることが明記されました。これは、fullName メソッドのロジックと整合性を保つためのドキュメントの改善です。

これらの変更により、SSAのテキスト表現がよりGo言語の慣習に沿ったものとなり、可読性が向上しました。

関連リンク

参考にした情報源リンク

  • Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
  • Go言語のコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (コミットメッセージに記載されている https://golang.org/cl/7307105 は、このGerritの変更リストへのリンクです。)
  • Go言語のSSAに関するブログ記事や解説(一般的な情報源)