[インデックス 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
関数がこの新しいメソッドを利用するように変更した点にあります。
-
Function.fullName(from *Package) string
の導入:- この新しいメソッドは、
from
という*Package
型の引数を受け取ります。これは、現在参照を行っているコンテキストのパッケージを示します。 - メソッドの内部では、
f.Pkg
(現在の関数のパッケージ) とfrom
パッケージを比較します。 - もし
f.Pkg == from
であれば、つまり参照元と参照先の関数が同じパッケージに属している場合、pkgQual
という文字列変数を空 (""
) に設定します。 - そうでなければ(異なるパッケージからの参照の場合)、
pkgQual
にf.Pkg.ImportPath + "."
を設定し、パッケージ名を修飾子として含めます。 - 最終的な関数名の文字列は、この
pkgQual
を利用して構築されます。これにより、同じパッケージ内の参照ではパッケージ名が省略され、異なるパッケージからの参照ではパッケージ名が明示されるようになります。
- この新しいメソッドは、
-
Function.FullName()
の変更:- 既存の
FullName()
メソッドは、引数なしで呼び出されるため、常にパッケージ名を修飾する動作を維持する必要がありました。そのため、FullName()
は単にf.fullName(nil)
を呼び出すように変更されました。nil
を渡すことで、fullName
メソッド内でf.Pkg == nil
またはf.Pkg != from
の条件が常に真となり、パッケージ名が常に含まれるようになります。
- 既存の
-
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ダンプの出力時に、参照元と参照先の関数が同じパッケージに属していればパッケージ名が省略されるという、このコミットの主要な目的が達成されます。
-
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.Pkg
がnil
の場合は、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言語のSSAパッケージに関する公式ドキュメント(当時のもの、現在はGoコンパイラに統合): https://pkg.go.dev/golang.org/x/tools/go/ssa (これは現在のパッケージですが、当時の
exp/ssa
の後継にあたります) - Go言語のパッケージに関する公式ドキュメント: https://go.dev/doc/effective_go#packages
- SSA形式に関する一般的な情報 (Wikipedia): https://ja.wikipedia.org/wiki/%E9%9D%99%E7%9A%84%E5%8D%98%E4%B8%80%E4%BB%A3%E5%85%A5%E5%BD%A2%E5%BC%8F
参考にした情報源リンク
- Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
- Go言語のコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (コミットメッセージに記載されている
https://golang.org/cl/7307105
は、このGerritの変更リストへのリンクです。) - Go言語のSSAに関するブログ記事や解説(一般的な情報源)