[インデックス 15465] ファイルの概要
このコミットは、GoコンパイラのSSA (Static Single Assignment) フォーム生成部分における、以前のコミット(CL 7395052)で導入された重要な変更が、別のコミット(CL 7371051)によって誤って元に戻されてしまった「マージの失敗」を修正するものです。具体的には、構造体のフィールド選択(特に匿名フィールドのプロモーション)をSSA形式で適切に処理するためのロジックを復元し、関連するコードのクリーンアップとロギングの改善も行っています。
コミット
commit 37cb6f809a9f1ba1ba2682b34eba6b86e9ffd80f
Author: Alan Donovan <adonovan@google.com>
Date: Wed Feb 27 11:39:39 2013 -0500
exp/ssa: resolve botched merge.
While submitting CL 7371051 I accidentally reverted much of CL
7395052. This change restores it.
R=gri
TBR=gri
CC=golang-dev
https://golang.org/cl/7364051
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/37cb6f809a9f1ba1ba2682b34eba6b86e9ffd80f
元コミット内容
exp/ssa: resolve botched merge.
While submitting CL 7371051 I accidentally reverted much of CL
7395052. This change restores it.
R=gri
TBR=gri
CC=golang-dev
https://golang.org/cl/7364051
変更の背景
このコミットの背景には、Goコンパイラの開発プロセスにおけるマージの複雑さが存在します。コミットメッセージによると、CL 7371051
("runtime: fix race in TestStackGrowth")が提出される際に、誤って以前の重要な変更であるCL 7395052
(これも"runtime: fix race in TestStackGrowth"に関連する可能性が高い)の大部分が元に戻されてしまいました。
CL 7395052
は、GoコンパイラのSSA(Static Single Assignment)フォーム生成フェーズにおいて、構造体のフィールド選択、特にGoの匿名フィールド(埋め込み)によるプロモーションされたフィールドの扱いに関する重要な改善を導入していたと考えられます。この改善は、SSA形式でのコード生成の正確性と効率性に寄与するものでした。
しかし、CL 7371051
の適用時に、このSSA関連の変更が意図せずリバートされてしまったため、コンパイラのSSA生成ロジックに問題が生じる可能性がありました。本コミット37cb6f809a9f1ba1ba2682b34eba6b86e9ffd80f
は、このマージの失敗を修正し、CL 7395052
によって導入されたSSA関連の変更を完全に復元することを目的としています。これにより、GoコンパイラのSSAバックエンドが意図した通りに機能し、正確なコードを生成できるようになります。
前提知識の解説
Go SSA (Static Single Assignment) Form
SSA(Static Single Assignment)フォームは、コンパイラ最適化の中間表現(IR)の一種です。SSA形式では、各変数が一度だけ代入されるという特性を持ちます。これにより、データフロー解析や最適化が大幅に簡素化されます。Goコンパイラは、ソースコードをAST(抽象構文木)にパースした後、SSA形式に変換し、様々な最適化を適用してから最終的な機械語コードを生成します。
src/pkg/exp/ssa
パッケージは、Goコンパイラの実験的なSSAバックエンドの実装を含んでいます。このパッケージは、GoプログラムのASTをSSA形式に変換し、その後の最適化やコード生成の基盤を提供します。
go/types
パッケージ
go/types
パッケージは、Go言語の型システムをプログラム的に表現・操作するための標準ライブラリです。Goのソースコードを解析し、各識別子や式の型情報を決定する際に使用されます。コンパイラは、このパッケージを利用して、変数、関数、構造体フィールドなどの型を正確に把握し、型チェックやコード生成を行います。
go/ast
パッケージ
go/ast
パッケージは、Go言語のソースコードを抽象構文木(AST)として表現するためのデータ構造と関数を提供します。ASTは、ソースコードの構文構造を木構造で表現したもので、コンパイラがソースコードを解析し、意味を理解するための最初のステップです。ast.SelectorExpr
は、obj.field
やpkg.Func
のようなセレクタ式を表すASTノードです。
Selector Expressions (ast.SelectorExpr
)
ast.SelectorExpr
は、GoのASTにおいて、セレクタ式(例: object.field
、package.Function
)を表す構造体です。これは、あるオブジェクトのフィールドやメソッド、あるいはパッケージの識別子にアクセスする際に使用されます。コンコンパイラは、このASTノードを解析して、どのフィールドや関数が参照されているかを特定し、適切なSSA命令を生成します。
Promoted Fields (Anonymous Fields/Embedded Fields)
Go言語には「匿名フィールド」または「埋め込みフィールド」という概念があります。これは、構造体の中に別の構造体やインターフェースをフィールド名なしで埋め込むことができる機能です。埋め込まれた構造体のフィールドやメソッドは、外側の構造体のフィールドであるかのように直接アクセスできます。これを「プロモーション(昇格)」と呼びます。
例えば、以下の構造体があるとします。
type Point struct {
X int
Y int
}
type Circle struct {
Point // 匿名フィールド
Radius int
}
func main() {
c := Circle{Point: Point{X: 10, Y: 20}, Radius: 5}
fmt.Println(c.X) // PointのXフィールドに直接アクセス
}
この場合、c.X
は実際にはc.Point.X
を意味します。コンパイラは、このようなプロモーションされたフィールドへのアクセスを適切に解決する必要があります。findPromotedField
のような関数は、このプロモーションパスを解決するために使用されます。
L-values and R-values
- L-value (Left-value): 代入演算子の左側に置くことができる式を指します。メモリ上の場所(アドレス)を持つものを表し、その値を変更することができます。変数、ポインタのデリファレンス、配列の要素、構造体のフィールドなどがL-valueになり得ます。例えば、
x = 10
のx
はL-valueです。 - R-value (Right-value): 代入演算子の右側に置くことができる式を指します。値そのものを表し、メモリ上の場所を持つ必要はありません。リテラル、定数、関数の戻り値などがR-valueになり得ます。例えば、
x = 10
の10
はR-valueです。
コンパイラは、L-valueとR-valueを区別して、それぞれに対して適切なSSA命令(アドレスの取得、値のロードなど)を生成します。
Race Condition
レースコンディション(競合状態)とは、複数の並行プロセスやスレッドが共有リソースにアクセスする際に、そのアクセス順序によって結果が非決定的に変わってしまう状態を指します。特に、読み書きが同時に行われる場合に問題となりやすく、予期せぬバグやデータ破損を引き起こす可能性があります。TestStackGrowth
のようなテストでレースコンディションが発生するということは、テストが並行して実行される際に、スタックの成長に関する内部状態が正しく同期されていないことを示唆しています。
技術的詳細
このコミットの主要な変更は、src/pkg/exp/ssa/builder.go
における構造体フィールドのアクセス処理の再構築です。以前はdemoteSelector
という関数が、プロモーションされたフィールドを含むセレクタ式を、プロモーションされていない形式に「降格」させてから処理していました。しかし、このアプローチは非効率的であり、ASTのミューテーションを伴うため、並行処理の妨げになる可能性がありました。
本コミットでは、demoteSelector
を廃止し、より汎用的で効率的なselector
、fieldAddr
、fieldExpr
の3つの関数に処理を分割・集約しています。
-
selector(fn *Function, e *ast.SelectorExpr, wantAddr, escaping bool) Value
:- この関数は、
ast.SelectorExpr
(セレクタ式、例:obj.field
)を評価し、その結果のSSAValue
を返します。 wantAddr
引数によって、フィールドのアドレス(L-value)が必要か、それとも値(R-value)が必要かを制御します。escaping
引数は、結果のポインタがエスケープ解析の対象となるかどうかを示します。- 内部で
findPromotedField
を使用して、プロモーションされたフィールドのパスを解決します。 - 最終的に、
wantAddr
の値に応じてfieldAddr
またはfieldExpr
を呼び出します。
- この関数は、
-
fieldAddr(fn *Function, base ast.Expr, path *anonFieldPath, index int, fieldType types.Type, escaping bool) Value
:- この関数は、構造体またはポインタ型構造体のベース式から、指定されたフィールドのアドレスをSSA
Value
として返します。 path
引数は、匿名フィールドを介したプロモーションパスを示します。index
は、最終的にアクセスするフィールドのインデックスです。- 再帰的に
fieldAddr
またはfieldExpr
を呼び出すことで、ネストされたプロモーションパスを処理します。 - 最終的なフィールドのアドレスを表す
FieldAddr
命令を生成し、fn.emit
でSSAグラフに追加します。
- この関数は、構造体またはポインタ型構造体のベース式から、指定されたフィールドのアドレスをSSA
-
fieldExpr(fn *Function, base ast.Expr, path *anonFieldPath, index int, fieldType types.Type) Value
:- この関数は、構造体またはポインタ型構造体のベース式から、指定されたフィールドの値をSSA
Value
として返します。 path
とindex
の役割はfieldAddr
と同様です。- ベースが構造体の場合(レジスタ内の非アドレス可能構造体)、
Field
命令を生成してフィールドの値を直接取得します。 - ベースがポインタ型構造体(アドレス可能構造体)の場合、
FieldAddr
命令でアドレスを取得し、そのアドレスから値をロードするためにemitLoad
を呼び出します。
- この関数は、構造体またはポインタ型構造体のベース式から、指定されたフィールドの値をSSA
これらの変更により、フィールドアクセスロジックがより明確にL-valueとR-valueの取得に分離され、SSA形式での表現がより直接的になりました。また、demoteSelector
がASTをミューテーションしていた問題も解消されています。
さらに、ロギングに関する小さな変更も含まれています。fmt.Fprintln(os.Stderr, ...)
のような直接的なエラー出力が、defer logStack(...)
という形式に置き換えられています。これは、デバッグ時のスタックトレース情報を含んだロギングをより統一的に行うための改善であり、src/pkg/exp/ssa/promote.go
からもos
パッケージのインポートが削除されています。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルと関数は以下の通りです。
-
src/pkg/exp/ssa/builder.go
:demoteSelector
関数の削除。selector
関数の新規追加。fieldAddr
関数の新規追加。fieldExpr
関数の新規追加。addr
関数内のdemoteSelector
呼び出しをb.selector(fn, e, true, escaping)
に置き換え。expr
関数内のdemoteSelector
呼び出しをb.selector(fn, e, false, false)
に置き換え。globalValueSpec
、buildFunction
、BuildPackage
関数内のデバッグロギングをfmt.Fprintln(os.Stderr, ...)
からdefer logStack(...)
形式に変更。
-
src/pkg/exp/ssa/promote.go
:os
パッケージのインポートを削除。buildMethodSet
関数内のデバッグロギングをfmt.Fprintf(os.Stderr, ...)
からdefer logStack(...)
形式に変更。
コアとなるコードの解説
src/pkg/exp/ssa/builder.go
selector
関数
func (b *Builder) selector(fn *Function, e *ast.SelectorExpr, wantAddr, escaping bool) Value {
id := makeId(e.Sel.Name, fn.Pkg.Types)
st := underlyingType(deref(b.exprType(e.X))).(*types.Struct)
index := -1
for i, f := range st.Fields {
if IdFromQualifiedName(f.QualifiedName) == id {
index = i
break
}
}
var path *anonFieldPath
if index == -1 {
// Not a named field. Use breadth-first algorithm.
path, index = findPromotedField(st, id)
if path == nil {
panic("field not found, even with promotion: " + e.Sel.Name)
}
}
fieldType := b.exprType(e)
if wantAddr {
return b.fieldAddr(fn, e.X, path, index, fieldType, escaping)
}
return b.fieldExpr(fn, e.X, path, index, fieldType)
}
このselector
関数は、ast.SelectorExpr
(例: s.f
)をSSA形式に変換する主要なエントリポイントです。
- まず、セレクタの識別子(
e.Sel.Name
)からフィールドのIDを生成します。 - ベース式
e.X
の型(構造体またはポインタ型構造体)を取得し、その構造体型st
から、直接指定されたフィールドid
のインデックスを探します。 - もし直接見つからなければ(
index == -1
)、Goの匿名フィールドのプロモーションルールに従って、findPromotedField
関数を使ってプロモーションパスpath
と最終的なフィールドのインデックスindex
を探索します。 - フィールドの型
fieldType
を取得します。 wantAddr
フラグに基づいて、フィールドのアドレスが必要な場合はfieldAddr
を、値が必要な場合はfieldExpr
を呼び出し、その結果を返します。
fieldAddr
関数
func (b *Builder) fieldAddr(fn *Function, base ast.Expr, path *anonFieldPath, index int, fieldType types.Type, escaping bool) Value {
var x Value
if path != nil {
switch underlyingType(path.field.Type).(type) {
case *types.Struct:
x = b.fieldAddr(fn, base, path.tail, path.index, path.field.Type, escaping)
case *types.Pointer:
x = b.fieldExpr(fn, base, path.tail, path.index, path.field.Type)
}
} else {
switch underlyingType(b.exprType(base)).(type) {
case *types.Struct:
x = b.addr(fn, base, escaping).(address).addr
case *types.Pointer:
x = b.expr(fn, base)
}
}
v := &FieldAddr{
X: x,
Field: index,
}
v.setType(pointer(fieldType))
return fn.emit(v)
}
このfieldAddr
関数は、指定されたベース式base
から、プロモーションパスpath
とフィールドインデックスindex
を介して、最終的なフィールドのアドレスをSSA Value
として生成します。
path
がnil
でない場合(プロモーションされたフィールドへのアクセスの場合)、再帰的にfieldAddr
またはfieldExpr
を呼び出して、中間パスのSSAValue
x
を取得します。- 中間フィールドが構造体の場合(値渡し)、そのフィールドのアドレスを再帰的に取得します。
- 中間フィールドがポインタの場合、そのフィールドの値を再帰的に取得します。
path
がnil
の場合(直接フィールドアクセスの場合)、ベース式base
の型に応じてb.addr
(アドレス取得)またはb.expr
(値取得)を呼び出して、ベースのSSAValue
x
を取得します。- 最終的に、
FieldAddr
というSSA命令を生成します。これは、X
(ベースのアドレスまたはポインタ)とField
(フィールドのインデックス)から、目的のフィールドのアドレスを計算する命令です。 - 生成された
FieldAddr
命令の型を設定し、fn.emit(v)
でSSAグラフにこの命令を追加し、その結果のSSAValue
を返します。
fieldExpr
関数
func (b *Builder) fieldExpr(fn *Function, base ast.Expr, path *anonFieldPath, index int, fieldType types.Type) Value {
var x Value
if path != nil {
x = b.fieldExpr(fn, base, path.tail, path.index, path.field.Type)
} else {
x = b.expr(fn, base)
}
switch underlyingType(x.Type()).(type) {
case *types.Struct:
v := &Field{
X: x,
Field: index,
}
v.setType(fieldType)
return fn.emit(v)
case *types.Pointer: // *struct
v := &FieldAddr{
X: x,
Field: index,
}
v.setType(pointer(fieldType))
return emitLoad(fn, fn.emit(v))
}
panic("unreachable")
}
このfieldExpr
関数は、指定されたベース式base
から、プロモーションパスpath
とフィールドインデックスindex
を介して、最終的なフィールドの値をSSA Value
として生成します。
path
がnil
でない場合、再帰的にfieldExpr
を呼び出して、中間パスのSSAValue
x
を取得します。path
がnil
の場合、ベース式base
の値をb.expr
で取得し、SSAValue
x
とします。- 取得した
x
の型に応じて処理を分岐します。*types.Struct
の場合(ベースが構造体そのもの、つまり値渡しの場合)、Field
というSSA命令を生成します。これは、X
(ベースの構造体値)とField
(フィールドのインデックス)から、目的のフィールドの値を直接取得する命令です。*types.Pointer
の場合(ベースが構造体へのポインタの場合)、まずFieldAddr
命令でフィールドのアドレスを取得します。その後、emitLoad
関数を呼び出して、そのアドレスから値をロードする命令を生成します。
- 生成されたSSA命令の型を設定し、
fn.emit(v)
でSSAグラフに追加し、その結果のSSAValue
を返します。
addr
および expr
関数の変更
addr
関数(L-valueのSSA変換)とexpr
関数(R-valueのSSA変換)は、ast.SelectorExpr
を処理する部分で、以前のb.demoteSelector
の呼び出しを、新しく導入されたb.selector
の呼び出しに置き換えました。
addr
関数内では、フィールドのアドレスが必要なのでb.selector(fn, e, true, escaping)
を呼び出します。expr
関数内では、フィールドの値が必要なのでb.selector(fn, e, false, false)
を呼び出します。
これにより、フィールドアクセスロジックが一元化され、demoteSelector
の複雑な処理が不要になりました。
ロギングの変更
globalValueSpec
、buildFunction
、BuildPackage
、buildMethodSet
といった関数では、デバッグ目的のロギング出力がfmt.Fprintln(os.Stderr, ...)
やfmt.Fprintf(os.Stderr, ...)
からdefer logStack(...)
という形式に変更されました。これは、Goのdefer
ステートメントとlogStack
関数を組み合わせることで、関数の開始と終了時に自動的にロギングを行い、特にデバッグ時に呼び出しスタックの情報を含めるなど、より構造化されたロギングを可能にするための改善です。これにより、src/pkg/exp/ssa/promote.go
からos
パッケージのインポートが不要になりました。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/37cb6f809a9f1ba1ba2682b34eba6b86e9ffd80f
- Go CL 7364051: https://golang.org/cl/7364051
参考にした情報源リンク
- Web search results for "golang CL 7371051":
CL 7371051, titled "runtime: fix race in TestStackGrowth", addresses a race condition within the TestStackGrowth test in the Go runtime. This commit ensures the test runs reliably by preventing concurrent access issues that could lead to intermittent failures.
- Web search results for "golang CL 7395052":
CL 7395052 is titled "runtime: fix race in TestStackGrowth". This commit addresses a race condition found in the TestStackGrowth test within the Go runtime.