[インデックス 15372] ファイルの概要
このコミットは、Go言語の実験的なSSA (Static Single Assignment) パッケージ (exp/ssa
) における、いくつかの残存するTODO項目を解消することを目的としています。具体的には、append
、delete
、real
、imag
といった組み込み関数の処理に関する修正や改善、および関連するテストの追加、不要なTODOコメントの削除、そしてインタープリタの挙動に関するコメントの追加が含まれています。
コミット
commit d9001ef012aabcd28c66a2d1f321078f45a2ffac
Author: Alan Donovan <adonovan@google.com>
Date: Fri Feb 22 00:09:21 2013 -0500
exp/ssa: cross off a few remaining TODO issues.
- append: nothing to do (nonsemantic change).
- delete: now performs correct conversion (+ test).
- emitCompare: nothing to do.
- emitArith (shifts): nothing to do (+ test).
- "banish untyped types": give up on that.
- real, imag: now do correct conversions.
- added comment to interp.go re zero-size values.
R=gri
CC=golang-dev
https://golang.org/cl/7391046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d9001ef012aabcd28c66a2d1f321078f45a2ffac
元コミット内容
このコミットは、exp/ssa
パッケージ内のTODOリストからいくつかの項目を削除し、関連する修正を適用しています。
append
組み込み関数: 意味論的な変更は不要と判断され、コードの微調整が行われました。delete
組み込み関数: マップのキーの型変換が正しく行われるように修正され、そのためのテストが追加されました。emitCompare
関数: 以前のTODO項目は不要と判断され、削除されました。emitArith
関数 (シフト演算): 以前のTODO項目は不要と判断され、シフト演算に関するテストが追加されました。- 「untyped types(型なし型)の排除」: この目標は断念されました。
real
およびimag
組み込み関数: 複素数型からの正しい型変換が行われるように修正されました。interp.go
にゼロサイズ値に関するコメントが追加されました。
変更の背景
このコミットは、GoコンパイラのSSAバックエンドの初期開発段階における継続的な改善の一環です。exp/ssa
パッケージは、GoプログラムをSSA形式に変換し、最適化を行うための実験的な実装でした。開発プロセスでは、多くのTODO項目が残されており、それらを一つずつ解決していく必要がありました。
特に、組み込み関数(append
, delete
, real
, imag
など)は、Go言語のセマンティクスに厳密に従う必要があり、SSA形式への変換時に正確な型変換や挙動を保証することが重要です。これらの関数がコンパイラのSSAパスで正しく扱われない場合、生成されるコードの誤りや非効率性につながる可能性があります。
また、「untyped types(型なし型)の排除」というTODO項目が断念されたことは、Goの型システムにおける「型なし定数」の重要性と、それらをSSAレベルで完全に排除することの難しさ、あるいは不必要性を示唆しています。型なし定数は、Goの柔軟な型推論と数値リテラルの扱いにおいて中心的な役割を果たします。
interp.go
へのコメント追加は、SSAインタープリタの特定の挙動、特にゼロサイズ値の扱いに関する潜在的なパフォーマンス特性を文書化するものです。これは、インタープリタの正確性と性能特性を理解するために重要です。
前提知識の解説
このコミットを理解するためには、以下の概念についての知識が役立ちます。
-
SSA (Static Single Assignment) 形式:
- コンパイラ最適化の中間表現の一つで、各変数が一度だけ代入されるように変換された形式です。これにより、データフロー解析や最適化が容易になります。
- Goコンパイラは、プログラムをSSA形式に変換し、様々な最適化(デッドコード削除、定数畳み込み、レジスタ割り当てなど)を適用します。
exp/ssa
パッケージは、Go言語のソースコードからSSA形式のグラフを構築するための実験的なライブラリでした。
-
Go言語の組み込み関数 (Builtin Functions):
append
: スライスに要素を追加し、新しいスライスを返します。容量が足りない場合は新しい基底配列を割り当てます。delete
: マップからキーとそれに対応する値を削除します。len
,cap
: スライス、マップ、チャネル、配列などの長さや容量を返します。real
,imag
: 複素数型の実部と虚部を浮動小数点数で返します。print
,println
: デバッグ目的で値を標準出力に出力します。close
: チャネルを閉じます。copy
: スライス間で要素をコピーします。- これらの関数は言語仕様の一部であり、コンパイラによって特別に扱われます。
-
Go言語の型システム:
- 型なし型 (Untyped Types): Go言語には、数値リテラル(例:
10
,3.14
,1i
)や特定の定数(nil
,true
,false
)に適用される「型なし」という概念があります。これらは、使用される文脈によって適切な型に暗黙的に変換されます。例えば、var x float64 = 10
の10
は型なし整数リテラルであり、float64
型に変換されます。 - 基本型:
int
,float32
,float64
,complex64
,complex128
など、Goが提供する基本的なデータ型です。 types.Type
:go/types
パッケージで定義されるインターフェースで、Go言語の型の抽象表現です。コンパイラやツールが型情報を扱う際に使用されます。token.Token
:go/token
パッケージで定義される列挙型で、Go言語の字句解析(トークナイズ)によって生成されるトークン(演算子、キーワードなど)を表します。
- 型なし型 (Untyped Types): Go言語には、数値リテラル(例:
-
抽象構文木 (AST):
go/ast
パッケージで定義されるデータ構造で、Goプログラムのソースコードをツリー構造で表現したものです。コンパイラのフロントエンドがソースコードを解析してASTを構築し、その後のSSA変換などのパスで利用されます。ast.CallExpr
: ASTノードの一つで、関数呼び出しを表します。
-
ゼロサイズ値:
- Go言語には、
struct{}
(空の構造体)や空の配列など、メモリを占有しないとされる型が存在します。これらは「ゼロサイズ値」と呼ばれます。 - しかし、インタープリタの実装によっては、これらのゼロサイズ値も内部的に何らかの表現を持つ必要があり、それがパフォーマンスに影響を与える可能性があります。
- Go言語には、
技術的詳細
このコミットの技術的詳細は、主にexp/ssa
パッケージのbuilder.go
ファイルにおける組み込み関数の処理ロジックの改善と、emit.go
およびinterp/interp.go
におけるTODOコメントの整理、そしてinterp/testdata/coverage.go
におけるテストカバレッジの拡充にあります。
builder.go
の変更点
builder.go
は、GoのASTからSSA形式のグラフを構築する主要なロジックを含んでいます。
-
TODOコメントの整理:
- 以前のTODOリストから、
append, delete details.
,finish emitCompare, emitArith.
,banish "untyped" types everywhere except package/universal constants?
,polish.
,tests.
といった項目が削除されました。これは、これらの問題が解決されたか、あるいは解決が不要/不可能と判断されたことを示しています。
- 以前のTODOリストから、
-
新しい基本型の追加:
tComplex64
とtComplex128
というtypes.Type
の定数が追加されました。これは、複素数型をSSA構築時に明示的に扱うための準備です。
-
setCall
関数の改善:setCall
関数は、組み込み関数呼び出しのASTノード(ast.CallExpr
)をSSA形式に変換する役割を担っています。append
: 以前のTODOコメントが削除され、append([]byte, string...) []byte
のような特殊なケースに関する言及もなくなりました。コードは、append
の引数型推論ロジックを微調整しています。特に、c.HasEllipsis
(...
を使った可変長引数呼び出し)の有無によって、引数の処理方法が分岐するようになりました。delete
: 以前は「TODO(adonovan): fix: this is incorrect.」とコメントされていた部分が修正されました。- 修正前は、マップとキーの型変換を
nil
(変換なし)として扱っていました。 - 修正後は、
tkey := underlyingType(b.exprType(args[0])).(*types.Map).Key
を用いて、マップのキーの実際の型を取得し、その型(tkey
)に基づいてキーの引数に型変換を適用するように変更されました。これにより、delete
のキー引数に対する正しい型変換が保証されます。
- 修正前は、マップとキーの型変換を
real
,imag
: 以前は「TODO(adonovan): fix: apply reverse conversion to "complex" case below.」とコメントされていました。- 修正後は、
e
(real
またはimag
の呼び出し結果)の型に基づいて、元の複素数引数の型を推論し、その型(argType
)をbptypes
(組み込み関数の仮引数型)に追加するように変更されました。 - 具体的には、結果の型が
UntypedFloat
であればUntypedComplex
、Float64
であればComplex128
、Float32
であればComplex64
と推論します。これにより、real
やimag
が受け取る複素数引数に対する正しい型変換がSSAレベルで適用されるようになります。
- 修正後は、
emit.go
の変更点
emit.go
は、SSA命令を生成するロジックを含んでいます。
emitArith
(シフト演算):- シフト演算(
token.SHL
,token.SHR
)に関する「TODO(adonovan): fix: is this correct?」というコメントが削除されました。これは、シフト演算のSSA生成ロジックが正しいと判断されたことを意味します。
- シフト演算(
emitCompare
:- 比較演算に関する「TODO(adonovan): fix: this is incomplete.」というコメントが削除されました。これは、比較演算のSSA生成ロジックが完全であると判断されたことを意味します。
interp/interp.go
の変更点
interp/interp.go
は、SSA形式のコードを解釈実行するインタープリタのロジックを含んでいます。
- ゼロサイズ値に関するコメント追加:
- インタープリタの特性に関するコメントブロックに、以下の行が追加されました。
// * all values occupy space, even those of types defined by the spec // to have zero size, e.g. struct{}. This can cause asymptotic // performance degradation.
- これは、Go言語の仕様上はゼロサイズとされる型(例:
struct{}
)であっても、このSSAインタープリタの実装では内部的に何らかのメモリ空間を占有する可能性があり、それが漸近的なパフォーマンス低下を引き起こす可能性があることを明記しています。これは、インタープリタの設計上のトレードオフや限界を文書化したものです。
- インタープリタの特性に関するコメントブロックに、以下の行が追加されました。
interp/testdata/coverage.go
の変更点
interp/testdata/coverage.go
は、SSAインタープリタのテストカバレッジを向上させるためのテストケースを含んでいます。
- シフト演算のテスト追加:
// Shifts.
というコメントの下に、int64
とuint64
の変数を用いたシフト演算のテストケースが追加されました。これにより、SSAインタープリタがシフト演算を正しく処理できることが検証されます。
delete
のキー暗黙的変換のテスト追加:// Implicit conversion of delete() key operand.
というコメントの下に、delete
組み込み関数のキー引数に対する暗黙的な型変換を検証するテストケースが追加されました。map[I]bool
(I
はinterface{}
)というマップを使用し、delete(m, I(1))
とdelete(m, 2)
のように、インターフェース型と基本型のリテラルをキーとして削除する操作をテストしています。これにより、delete
が異なる型のキーを正しく処理できることが確認されます。
コアとなるコードの変更箇所
src/pkg/exp/ssa/builder.go
--- a/src/pkg/exp/ssa/builder.go
+++ b/src/pkg/exp/ssa/builder.go
@@ -31,15 +31,10 @@ package ssa
// It uses a mutex so that access from multiple threads is serialized.\n \n // TODO(adonovan): fix the following:\n-// - append, delete details.\n // - support f(g()) where g has multiple result parameters.\n-// - finish emitCompare, emitArith.\n-// - banish \"untyped\" types everywhere except package/universal constants?\n // - concurrent SSA code generation of multiple packages.\n // - consider function-local NamedTypes.\n // They can have nonempty method-sets due to promotion. Test.\n-// - polish.\n-// - tests.\n \n import (\n \t\"fmt\"\n@@ -58,6 +53,8 @@ var (\n \ttByte = types.Typ[types.Byte]\n \ttFloat32 = types.Typ[types.Float32]\n \ttFloat64 = types.Typ[types.Float64]\n+\ttComplex64 = types.Typ[types.Complex64]\n+\ttComplex128 = types.Typ[types.Complex128]\n \ttInt = types.Typ[types.Int]\n \ttInvalid = types.Typ[types.Invalid]\n \ttUntypedNil = types.Typ[types.UntypedNil]\n@@ -1128,17 +1125,21 @@ func (b *Builder) setCall(fn *Function, e *ast.CallExpr, c *CallCommon) {\n \t\tvar bptypes []types.Type // formal parameter types of builtins\n \t\tswitch builtin := e.Fun.(*ast.Ident).Name; builtin {\n \t\tcase \"append\":\n-\t\t\t// append([]T, ...T) []T\n-\t\t\t// append([]byte, string...) []byte // TODO(adonovan): fix: support.\n \t\t\t// Infer arg types from result type:\n \t\t\trt := b.exprType(e)\n-\t\t\tvt = underlyingType(rt).(*types.Slice).Elt // variadic\n-\t\t\tif !c.HasEllipsis {\n+\t\t\tbptypes = append(bptypes, rt)\n+\t\t\tif c.HasEllipsis {\n+\t\t\t\t// 2-arg \'...\' call form. No conversions.\n+\t\t\t\t// append([]T, []T) []T\n+\t\t\t\t// append([]byte, string) []byte\n+\t\t\t} else {\n+\t\t\t\t// variadic call form.\n+\t\t\t\t// append([]T, ...T) []T\n \t\t\t\targs, varargs = args[:1], args[1:]\n+\t\t\t\tvt = underlyingType(rt).(*types.Slice).Elt\n \t\t\t}\n-\t\t\tbptypes = append(bptypes, rt)\n \t\tcase \"close\":\n-\t\t\tbptypes = append(bptypes, nil) // no conv\n+\t\t\t// no conv\n \t\tcase \"copy\":\n \t\t\t// copy([]T, []T) int\n \t\t\t// Infer arg types from each other. Sleazy.\n@@ -1151,22 +1152,33 @@ func (b *Builder) setCall(fn *Function, e *ast.CallExpr, c *CallCommon) {\n \t\t\t}\n \t\tcase \"delete\":\n \t\t\t// delete(map[K]V, K)\n-\t\t\t// TODO(adonovan): fix: this is incorrect.\n-\t\t\tbptypes = append(bptypes, nil) // map\n-\t\t\tbptypes = append(bptypes, nil) // key\n+\t\t\ttkey := underlyingType(b.exprType(args[0])).(*types.Map).Key\n+\t\t\tbptypes = append(bptypes, nil) // map: no conv\n+\t\t\tbptypes = append(bptypes, tkey) // key\n \t\tcase \"print\", \"println\": // print{,ln}(any, ...any)\n \t\t\tvt = tEface // variadic\n \t\t\tif !c.HasEllipsis {\n \t\t\t\targs, varargs = args[:1], args[1:]\n \t\t\t}\n \t\tcase \"len\":\n-\t\t\tbptypes = append(bptypes, nil) // no conv\n+\t\t\t// no conv\n \t\tcase \"cap\":\n-\t\t\tbptypes = append(bptypes, nil) // no conv\n+\t\t\t// no conv\n \t\tcase \"real\", \"imag\":\n-\t\t\t// TODO(adonovan): fix: apply reverse conversion\n-\t\t\t// to \"complex\" case below.\n-\t\t\tbptypes = append(bptypes, nil)\n+\t\t\t// Reverse conversion to \"complex\" case below.\n+\t\t\t// Typechecker, help us out. :(\n+\t\t\tvar argType types.Type\n+\t\t\tswitch b.exprType(e).(*types.Basic).Kind {\n+\t\t\tcase types.UntypedFloat:\n+\t\t\t\targType = types.Typ[types.UntypedComplex]\n+\t\t\tcase types.Float64:\n+\t\t\t\targType = tComplex128\n+\t\t\tcase types.Float32:\n+\t\t\t\targType = tComplex64\n+\t\t\tdefault:\n+\t\t\t\tunreachable()\n+\t\t\t}\n+\t\t\tbptypes = append(bptypes, argType, argType)\n \t\tcase \"complex\":\n \t\t\t// Typechecker, help us out. :(\n \t\t\tvar argType types.Type
src/pkg/exp/ssa/emit.go
--- a/src/pkg/exp/ssa/emit.go
+++ b/src/pkg/exp/ssa/emit.go
@@ -34,7 +34,6 @@ func emitLoad(f *Function, addr Value) Value {\n func emitArith(f *Function, op token.Token, x, y Value, t types.Type) Value {\n \tswitch op {\n \tcase token.SHL, token.SHR:\n-\t\t// TODO(adonovan): fix: is this correct?\n \t\tx = emitConv(f, x, t)\n \t\ty = emitConv(f, y, types.Typ[types.Uint64])\n \n@@ -59,7 +58,6 @@ func emitArith(f *Function, op token.Token, x, y Value, t types.Type) Value {\n // comparison comparison \'x op y\'.\n //\n func emitCompare(f *Function, op token.Token, x, y Value) Value {\n-\t// TODO(adonovan): fix: this is incomplete.\n \txt := underlyingType(x.Type())\n \tyt := underlyingType(y.Type())\n \n```
### `src/pkg/exp/ssa/interp/interp.go`
```diff
--- a/src/pkg/exp/ssa/interp/interp.go
+++ b/src/pkg/exp/ssa/interp/interp.go
@@ -35,6 +35,10 @@\n // program are assumed to be the same as those of the interpreter\n // itself.\n //\n+// * all values occupy space, even those of types defined by the spec\n+// to have zero size, e.g. struct{}. This can cause asymptotic\n+// performance degradation.\n+//\n // * os.Exit is implemented using panic, causing deferred functions to\n // run.\n package interp
src/pkg/exp/ssa/interp/testdata/coverage.go
--- a/src/pkg/exp/ssa/interp/testdata/coverage.go
+++ b/src/pkg/exp/ssa/interp/testdata/coverage.go
@@ -292,3 +292,31 @@ func init() {\n \t\tpanic(c)\n \t}\n }\n+\n+// Shifts.\n+func init() {\n+\tvar i int64 = 1\n+\tvar u uint64 = 1 << 32\n+\tif x := i << uint32(u); x != 1 {\n+\t\tpanic(x)\n+\t}\n+\tif x := i << uint64(u); x != 0 {\n+\t\tpanic(x)\n+\t}\n+}\n+\n+// Implicit conversion of delete() key operand.\n+func init() {\n+\ttype I interface{}\n+\tm := make(map[I]bool)\n+\tm[1] = true\n+\tm[I(2)] = true\n+\tif len(m) != 2 {\n+\t\tpanic(m)\n+\t}\n+\tdelete(m, I(1))\n+\tdelete(m, 2)\n+\tif len(m) != 0 {\n+\t\tpanic(m)\n+\t}\n+}\n```
## コアとなるコードの解説
このコミットの主要な変更は、`builder.go`内の`setCall`関数における組み込み関数`delete`、`real`、`imag`の処理ロジックの改善です。
1. **`delete`関数の修正**:
* 以前のコードでは、`delete`のキー引数に対する型変換を適切に行っていませんでした。
* 修正後のコードでは、`b.exprType(args[0])`でマップの型を取得し、`underlyingType(...).(*types.Map).Key`を使ってマップのキーの基底型を抽出しています。この抽出されたキー型`tkey`が、`bptypes`(組み込み関数の仮引数型)の2番目の要素として追加されます。これにより、`delete`関数が呼び出された際に、キー引数がマップのキー型に正しく変換されるようになります。これは、Goのマップが厳密なキー型チェックを行うため、SSA変換時にこの変換を正確に表現することが不可欠です。
2. **`real`および`imag`関数の修正**:
* これらの関数は複素数型を引数に取り、その実部または虚部を浮動小数点数で返します。以前のSSA変換ロジックでは、引数の型変換が不完全でした。
* 修正後のコードでは、`b.exprType(e).(*types.Basic).Kind`を使って`real`または`imag`の呼び出し結果の型(これは浮動小数点数型になります)を調べます。
* その結果の型に基づいて、元の複素数引数が`UntypedComplex`、`Complex128`、または`Complex64`のいずれであったかを推論し、その型を`argType`として設定します。
* そして、この`argType`を`bptypes`に2回追加します(`real`と`imag`はそれぞれ1つの引数を取るが、`complex`関数との対称性のためか、2つの引数型を期待する形になっている)。これにより、SSAレベルで`real`や`imag`の引数に対する正しい型変換が適用され、複素数演算の正確性が保証されます。
3. **テストの追加**:
* `coverage.go`に追加されたテストは、これらの修正が正しく機能することを検証します。特に、`delete`のキー引数の暗黙的な型変換(例: `delete(m, 2)`で`int`リテラルが`interface{}`に変換されるケース)や、シフト演算の挙動がSSAインタープリタで正しく再現されることを確認しています。
これらの変更は、GoコンパイラのSSAバックエンドが、Go言語の組み込み関数のセマンティクスをより正確に表現し、より堅牢なコード生成を行うための重要なステップです。
## 関連リンク
* Go言語のSSAに関する公式ドキュメント(もしあれば、`exp/ssa`が最終的に統合された後の情報)
* Go言語の組み込み関数に関する公式ドキュメント
* Go言語の型システムに関する公式ドキュメント
## 参考にした情報源リンク
* Go言語の公式ドキュメント: [https://go.dev/doc/](https://go.dev/doc/)
* GoコンパイラのSSAに関するブログ記事や解説(一般的なSSAの概念を含む)
* Go言語の型なし定数に関する解説記事
* Go言語の`go/types`パッケージのドキュメント
* Go言語の`go/ast`パッケージのドキュメント
* Go言語の`go/token`パッケージのドキュメント
# [インデックス 15372] ファイルの概要
このコミットは、Go言語の実験的なSSA (Static Single Assignment) パッケージ (`exp/ssa`) における、いくつかの残存するTODO項目を解消することを目的としています。具体的には、`append`、`delete`、`real`、`imag`といった組み込み関数の処理に関する修正や改善、および関連するテストの追加、不要なTODOコメントの削除、そしてインタープリタの挙動に関するコメントの追加が含まれています。
## コミット
commit d9001ef012aabcd28c66a2d1f321078f45a2ffac Author: Alan Donovan adonovan@google.com Date: Fri Feb 22 00:09:21 2013 -0500
exp/ssa: cross off a few remaining TODO issues.
- append: nothing to do (nonsemantic change).
- delete: now performs correct conversion (+ test).
- emitCompare: nothing to do.
- emitArith (shifts): nothing to do (+ test).
- "banish untyped types": give up on that.
- real, imag: now do correct conversions.
- added comment to interp.go re zero-size values.
R=gri
CC=golang-dev
https://golang.org/cl/7391046
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/d9001ef012aabcd28c66a2d1f321078f45a2ffac](https://github.com/golang/go/commit/d9001ef012aabcd28c66a2d1f321078f45a2ffac)
## 元コミット内容
このコミットは、`exp/ssa`パッケージ内のTODOリストからいくつかの項目を削除し、関連する修正を適用しています。
- `append` 組み込み関数: 意味論的な変更は不要と判断され、コードの微調整が行われました。
- `delete` 組み込み関数: マップのキーの型変換が正しく行われるように修正され、そのためのテストが追加されました。
- `emitCompare` 関数: 以前のTODO項目は不要と判断され、削除されました。
- `emitArith` 関数 (シフト演算): 以前のTODO項目は不要と判断され、シフト演算に関するテストが追加されました。
- 「untyped types(型なし型)の排除」: この目標は断念されました。
- `real` および `imag` 組み込み関数: 複素数型からの正しい型変換が行われるように修正されました。
- `interp.go` にゼロサイズ値に関するコメントが追加されました。
## 変更の背景
このコミットは、GoコンパイラのSSAバックエンドの初期開発段階における継続的な改善の一環です。`exp/ssa`パッケージは、GoプログラムをSSA形式に変換し、最適化を行うための実験的な実装でした。開発プロセスでは、多くのTODO項目が残されており、それらを一つずつ解決していく必要がありました。
特に、組み込み関数(`append`, `delete`, `real`, `imag`など)は、Go言語のセマンティクスに厳密に従う必要があり、SSA形式への変換時に正確な型変換や挙動を保証することが重要です。これらの関数がコンパイラのSSAパスで正しく扱われない場合、生成されるコードの誤りや非効率性につながる可能性があります。
また、「untyped types(型なし型)の排除」というTODO項目が断念されたことは、Goの型システムにおける「型なし定数」の重要性と、それらをSSAレベルで完全に排除することの難しさ、あるいは不必要性を示唆しています。型なし定数は、Goの柔軟な型推論と数値リテラルの扱いにおいて中心的な役割を果たします。
`interp.go`へのコメント追加は、SSAインタープリタの特定の挙動、特にゼロサイズ値の扱いに関する潜在的なパフォーマンス特性を文書化するものです。これは、インタープリタの正確性と性能特性を理解するために重要です。
## 前提知識の解説
このコミットを理解するためには、以下の概念についての知識が役立ちます。
1. **SSA (Static Single Assignment) 形式**:
* コンパイラ最適化の中間表現の一つで、各変数が一度だけ代入されるように変換された形式です。これにより、データフロー解析や最適化が容易になります。
* Goコンパイラは、プログラムをSSA形式に変換し、様々な最適化(デッドコード削除、定数畳み込み、レジスタ割り当てなど)を適用します。
* `exp/ssa`パッケージは、Go言語のソースコードからSSA形式のグラフを構築するための実験的なライブラリでした。Go 1.7でSSAが導入され、現在は`cmd/compile/internal/ssa/`にその実装があります。
2. **Go言語の組み込み関数 (Builtin Functions)**:
* `append`: スライスに要素を追加し、新しいスライスを返します。容量が足りない場合は新しい基底配列を割り当てます。
* `delete`: マップからキーとそれに対応する値を削除します。
* `len`, `cap`: スライス、マップ、チャネル、配列などの長さや容量を返します。
* `real`, `imag`: 複素数型の実部と虚部を浮動小数点数で返します。
* `print`, `println`: デバッグ目的で値を標準出力に出力します。
* `close`: チャネルを閉じます。
* `copy`: スライス間で要素をコピーします。
* これらの関数は言語仕様の一部であり、コンパイラによって特別に扱われます。
3. **Go言語の型システム**:
* **型なし型 (Untyped Types)**: Go言語には、数値リテラル(例: `10`, `3.14`, `1i`)や特定の定数(`nil`, `true`, `false`)に適用される「型なし」という概念があります。これらは、使用される文脈によって適切な型に暗黙的に変換されます。例えば、`var x float64 = 10` の `10` は型なし整数リテラルであり、`float64`型に変換されます。
* **基本型**: `int`, `float32`, `float64`, `complex64`, `complex128` など、Goが提供する基本的なデータ型です。
* **`types.Type`**: `go/types`パッケージで定義されるインターフェースで、Go言語の型の抽象表現です。コンパイラやツールが型情報を扱う際に使用されます。
* **`token.Token`**: `go/token`パッケージで定義される列挙型で、Go言語の字句解析(トークナイズ)によって生成されるトークン(演算子、キーワードなど)を表します。
4. **抽象構文木 (AST)**:
* `go/ast`パッケージで定義されるデータ構造で、Goプログラムのソースコードをツリー構造で表現したものです。コンパイラのフロントエンドがソースコードを解析してASTを構築し、その後のSSA変換などのパスで利用されます。
* `ast.CallExpr`: ASTノードの一つで、関数呼び出しを表します。
5. **ゼロサイズ値**:
* Go言語には、`struct{}`(空の構造体)や空の配列など、メモリを占有しないとされる型が存在します。これらは「ゼロサイズ値」と呼ばれます。
* しかし、インタープリタの実装によっては、これらのゼロサイズ値も内部的に何らかの表現を持つ必要があり、それがパフォーマンスに影響を与える可能性があります。
## 技術的詳細
このコミットの技術的詳細は、主に`exp/ssa`パッケージの`builder.go`ファイルにおける組み込み関数の処理ロジックの改善と、`emit.go`および`interp/interp.go`におけるTODOコメントの整理、そして`interp/testdata/coverage.go`におけるテストカバレッジの拡充にあります。
### `builder.go`の変更点
`builder.go`は、GoのASTからSSA形式のグラフを構築する主要なロジックを含んでいます。
1. **TODOコメントの整理**:
* 以前のTODOリストから、`append, delete details.`, `finish emitCompare, emitArith.`, `banish "untyped" types everywhere except package/universal constants?`, `polish.`, `tests.` といった項目が削除されました。これは、これらの問題が解決されたか、あるいは解決が不要/不可能と判断されたことを示しています。
2. **新しい基本型の追加**:
* `tComplex64`と`tComplex128`という`types.Type`の定数が追加されました。これは、複素数型をSSA構築時に明示的に扱うための準備です。
3. **`setCall`関数の改善**:
* `setCall`関数は、組み込み関数呼び出しのASTノード(`ast.CallExpr`)をSSA形式に変換する役割を担っています。
* **`append`**: 以前のTODOコメントが削除され、`append([]byte, string...) []byte`のような特殊なケースに関する言及もなくなりました。コードは、`append`の引数型推論ロジックを微調整しています。特に、`c.HasEllipsis`(`...`を使った可変長引数呼び出し)の有無によって、引数の処理方法が分岐するようになりました。
* **`delete`**: 以前は「TODO(adonovan): fix: this is incorrect.」とコメントされていた部分が修正されました。
* 修正前は、マップとキーの型変換を`nil`(変換なし)として扱っていました。
* 修正後は、`tkey := underlyingType(b.exprType(args[0])).(*types.Map).Key` を用いて、マップのキーの実際の型を取得し、その型(`tkey`)に基づいてキーの引数に型変換を適用するように変更されました。これにより、`delete`のキー引数に対する正しい型変換が保証されます。
* **`real`, `imag`**: 以前は「TODO(adonovan): fix: apply reverse conversion to "complex" case below.」とコメントされていました。
* 修正後は、`e`(`real`または`imag`の呼び出し結果)の型に基づいて、元の複素数引数の型を推論し、その型(`argType`)を`bptypes`(組み込み関数の仮引数型)に追加するように変更されました。
* 具体的には、結果の型が`UntypedFloat`であれば`UntypedComplex`、`Float64`であれば`Complex128`、`Float32`であれば`Complex64`と推論します。これにより、`real`や`imag`が受け取る複素数引数に対する正しい型変換がSSAレベルで適用されるようになります。
### `emit.go`の変更点
`emit.go`は、SSA命令を生成するロジックを含んでいます。
1. **`emitArith` (シフト演算)**:
* シフト演算(`token.SHL`, `token.SHR`)に関する「TODO(adonovan): fix: is this correct?」というコメントが削除されました。これは、シフト演算のSSA生成ロジックが正しいと判断されたことを意味します。
2. **`emitCompare`**:
* 比較演算に関する「TODO(adonovan): fix: this is incomplete.」というコメントが削除されました。これは、比較演算のSSA生成ロジックが完全であると判断されたことを意味します。
### `interp/interp.go`の変更点
`interp/interp.go`は、SSA形式のコードを解釈実行するインタープリタのロジックを含んでいます。
1. **ゼロサイズ値に関するコメント追加**:
* インタープリタの特性に関するコメントブロックに、以下の行が追加されました。
```go
// * all values occupy space, even those of types defined by the spec
// to have zero size, e.g. struct{}. This can cause asymptotic
// performance degradation.
```
* これは、Go言語の仕様上はゼロサイズとされる型(例: `struct{}`)であっても、このSSAインタープリタの実装では内部的に何らかのメモリ空間を占有する可能性があり、それが漸近的なパフォーマンス低下を引き起こす可能性があることを明記しています。これは、インタープリタの設計上のトレードオフや限界を文書化したものです。
### `interp/testdata/coverage.go`の変更点
`interp/testdata/coverage.go`は、SSAインタープリタのテストカバレッジを向上させるためのテストケースを含んでいます。
1. **シフト演算のテスト追加**:
* `// Shifts.` というコメントの下に、`int64`と`uint64`の変数を用いたシフト演算のテストケースが追加されました。これにより、SSAインタープリタがシフト演算を正しく処理できることが検証されます。
2. **`delete`のキー暗黙的変換のテスト追加**:
* `// Implicit conversion of delete() key operand.` というコメントの下に、`delete`組み込み関数のキー引数に対する暗黙的な型変換を検証するテストケースが追加されました。
* `map[I]bool`(`I`は`interface{}`)というマップを使用し、`delete(m, I(1))`と`delete(m, 2)`のように、インターフェース型と基本型のリテラルをキーとして削除する操作をテストしています。これにより、`delete`が異なる型のキーを正しく処理できることが確認されます。
## コアとなるコードの変更箇所
### `src/pkg/exp/ssa/builder.go`
```diff
--- a/src/pkg/exp/ssa/builder.go
+++ b/src/pkg/exp/ssa/builder.go
@@ -31,15 +31,10 @@ package ssa
// It uses a mutex so that access from multiple threads is serialized.\n \n // TODO(adonovan): fix the following:\n-// - append, delete details.\n // - support f(g()) where g has multiple result parameters.\n-// - finish emitCompare, emitArith.\n-// - banish \"untyped\" types everywhere except package/universal constants?\n // - concurrent SSA code generation of multiple packages.\n // - consider function-local NamedTypes.\n // They can have nonempty method-sets due to promotion. Test.\n-// - polish.\n-// - tests.\n \n import (\n \t\"fmt\"\n@@ -58,6 +53,8 @@ var (\n \ttByte = types.Typ[types.Byte]\n \ttFloat32 = types.Typ[types.Float32]\n \ttFloat64 = types.Typ[types.Float64]\n+\ttComplex64 = types.Typ[types.Complex64]\n+\ttComplex128 = types.Typ[types.Complex128]\n \ttInt = types.Typ[types.Int]\n \ttInvalid = types.Typ[types.Invalid]\n \ttUntypedNil = types.Typ[types.UntypedNil]\n@@ -1128,17 +1125,21 @@ func (b *Builder) setCall(fn *Function, e *ast.CallExpr, c *CallCommon) {\n \t\tvar bptypes []types.Type // formal parameter types of builtins\n \t\tswitch builtin := e.Fun.(*ast.Ident).Name; builtin {\n \t\tcase \"append\":\n-\t\t\t// append([]T, ...T) []T\n-\t\t\t// append([]byte, string...) []byte // TODO(adonovan): fix: support.\n \t\t\t// Infer arg types from result type:\n \t\t\trt := b.exprType(e)\n-\t\t\tvt = underlyingType(rt).(*types.Slice).Elt // variadic\n-\t\t\tif !c.HasEllipsis {\n+\t\t\tbptypes = append(bptypes, rt)\n+\t\t\tif c.HasEllipsis {\n+\t\t\t\t// 2-arg \'...\' call form. No conversions.\n+\t\t\t\t// append([]T, []T) []T\n+\t\t\t\t// append([]byte, string) []byte\n+\t\t\t} else {\n+\t\t\t\t// variadic call form.\n+\t\t\t\t// append([]T, ...T) []T\n \t\t\t\targs, varargs = args[:1], args[1:]\n+\t\t\t\tvt = underlyingType(rt).(*types.Slice).Elt\n \t\t\t}\n-\t\t\tbptypes = append(bptypes, rt)\n \t\tcase \"close\":\n-\t\t\tbptypes = append(bptypes, nil) // no conv\n+\t\t\t// no conv\n \t\tcase \"copy\":\n \t\t\t// copy([]T, []T) int\n \t\t\t// Infer arg types from each other. Sleazy.\n@@ -1151,22 +1152,33 @@ func (b *Builder) setCall(fn *Function, e *ast.CallExpr, c *CallCommon) {\n \t\t\t}\n \t\tcase \"delete\":\n \t\t\t// delete(map[K]V, K)\n-\t\t\t// TODO(adonovan): fix: this is incorrect.\n-\t\t\tbptypes = append(bptypes, nil) // map\n-\t\t\tbptypes = append(bptypes, nil) // key\n+\t\t\ttkey := underlyingType(b.exprType(args[0])).(*types.Map).Key\n+\t\t\tbptypes = append(bptypes, nil) // map: no conv\n+\t\t\tbptypes = append(bptypes, tkey) // key\n \t\tcase \"print\", \"println\": // print{,ln}(any, ...any)\n \t\t\tvt = tEface // variadic\n \t\t\tif !c.HasEllipsis {\n \t\t\t\targs, varargs = args[:1], args[1:]\n \t\t\t}\n \t\tcase \"len\":\n-\t\t\tbptypes = append(bptypes, nil) // no conv\n+\t\t\t// no conv\n \t\tcase \"cap\":\n-\t\t\tbptypes = append(bptypes, nil) // no conv\n+\t\t\t// no conv\n \t\tcase \"real\", \"imag\":\n-\t\t\t// TODO(adonovan): fix: apply reverse conversion\n-\t\t\t// to \"complex\" case below.\n-\t\t\tbptypes = append(bptypes, nil)\n+\t\t\t// Reverse conversion to \"complex\" case below.\n+\t\t\t// Typechecker, help us out. :(\n+\t\t\tvar argType types.Type\n+\t\t\tswitch b.exprType(e).(*types.Basic).Kind {\n+\t\t\tcase types.UntypedFloat:\n+\t\t\t\targType = types.Typ[types.UntypedComplex]\n+\t\t\tcase types.Float64:\n+\t\t\t\targType = tComplex128\n+\t\t\tcase types.Float32:\n+\t\t\t\targType = tComplex64\n+\t\t\tdefault:\n+\t\t\t\tunreachable()\n+\t\t\t}\n+\t\t\tbptypes = append(bptypes, argType, argType)\n \t\tcase \"complex\":\n \t\t\t// Typechecker, help us out. :(\n \t\t\tvar argType types.Type
src/pkg/exp/ssa/emit.go
--- a/src/pkg/exp/ssa/emit.go
+++ b/src/pkg/exp/ssa/emit.go
@@ -34,7 +34,6 @@ func emitLoad(f *Function, addr Value) Value {\n func emitArith(f *Function, op token.Token, x, y Value, t types.Type) Value {\n \tswitch op {\n \tcase token.SHL, token.SHR:\n-\t\t// TODO(adonovan): fix: is this correct?\n \t\tx = emitConv(f, x, t)\n \t\ty = emitConv(f, y, types.Typ[types.Uint64])\n \n@@ -59,7 +58,6 @@ func emitArith(f *Function, op token.Token, x, y Value, t types.Type) Value {\n // comparison comparison \'x op y\'.\n //\n func emitCompare(f *Function, op token.Token, x, y Value) Value {\n-\t// TODO(adonovan): fix: this is incomplete.\n \txt := underlyingType(x.Type())\n \tyt := underlyingType(y.Type())\n \n```
### `src/pkg/exp/ssa/interp/interp.go`
```diff
--- a/src/pkg/exp/ssa/interp/interp.go
+++ b/src/pkg/exp/ssa/interp/interp.go
@@ -35,6 +35,10 @@\n // program are assumed to be the same as those of the interpreter\n // itself.\n //\n+// * all values occupy space, even those of types defined by the spec\n+// to have zero size, e.g. struct{}. This can cause asymptotic\n+// performance degradation.\n+//\n // * os.Exit is implemented using panic, causing deferred functions to\n // run.\n package interp
src/pkg/exp/ssa/interp/testdata/coverage.go
--- a/src/pkg/exp/ssa/interp/testdata/coverage.go
+++ b/src/pkg/exp/ssa/interp/testdata/coverage.go
@@ -292,3 +292,31 @@ func init() {\n \t\tpanic(c)\n \t}\n }\n+\n+// Shifts.\n+func init() {\n+\tvar i int64 = 1\n+\tvar u uint64 = 1 << 32\n+\tif x := i << uint32(u); x != 1 {\n+\t\tpanic(x)\n+\t}\n+\tif x := i << uint64(u); x != 0 {\n+\t\tpanic(x)\n+\t}\n+}\n+\n+// Implicit conversion of delete() key operand.\n+func init() {\n+\ttype I interface{}\n+\tm := make(map[I]bool)\n+\tm[1] = true\n+\tm[I(2)] = true\n+\tif len(m) != 2 {\n+\t\tpanic(m)\n+\t}\n+\tdelete(m, I(1))\n+\tdelete(m, 2)\n+\tif len(m) != 0 {\n+\t\tpanic(m)\n+\t}\n+}\n```
## コアとなるコードの解説
このコミットの主要な変更は、`builder.go`内の`setCall`関数における組み込み関数`delete`、`real`、`imag`の処理ロジックの改善です。
1. **`delete`関数の修正**:
* 以前のコードでは、`delete`のキー引数に対する型変換を適切に行っていませんでした。
* 修正後のコードでは、`b.exprType(args[0])`でマップの型を取得し、`underlyingType(...).(*types.Map).Key`を使ってマップのキーの基底型を抽出しています。この抽出されたキー型`tkey`が、`bptypes`(組み込み関数の仮引数型)の2番目の要素として追加されます。これにより、`delete`関数が呼び出された際に、キー引数がマップのキー型に正しく変換されるようになります。これは、Goのマップが厳密なキー型チェックを行うため、SSA変換時にこの変換を正確に表現することが不可欠です。
2. **`real`および`imag`関数の修正**:
* これらの関数は複素数型を引数に取り、その実部または虚部を浮動小数点数で返します。以前のSSA変換ロジックでは、引数の型変換が不完全でした。
* 修正後のコードでは、`b.exprType(e).(*types.Basic).Kind`を使って`real`または`imag`の呼び出し結果の型(これは浮動小数点数型になります)を調べます。
* その結果の型に基づいて、元の複素数引数が`UntypedComplex`、`Complex128`、または`Complex64`のいずれであったかを推論し、その型を`argType`として設定します。
* そして、この`argType`を`bptypes`に2回追加します(`real`と`imag`はそれぞれ1つの引数を取るが、`complex`関数との対称性のためか、2つの引数型を期待する形になっている)。これにより、SSAレベルで`real`や`imag`の引数に対する正しい型変換が適用され、複素数演算の正確性が保証されます。
3. **テストの追加**:
* `coverage.go`に追加されたテストは、これらの修正が正しく機能することを検証します。特に、`delete`のキー引数の暗黙的な型変換(例: `delete(m, 2)`で`int`リテラルが`interface{}`に変換されるケース)や、シフト演算の挙動がSSAインタープリタで正しく再現されることを確認しています。
これらの変更は、GoコンパイラのSSAバックエンドが、Go言語の組み込み関数のセマンティクスをより正確に表現し、より堅牢なコード生成を行うための重要なステップです。
## 関連リンク
* Go言語の公式ドキュメント: [https://go.dev/doc/](https://go.dev/doc/)
* Go言語の組み込み関数: [https://go.dev/ref/spec#Predeclared_identifiers](https://go.dev/ref/spec#Predeclared_identifiers)
* Go言語の型システム: [https://go.dev/ref/spec#Types](https://go.dev/ref/spec#Types)
* GoコンパイラのSSAに関する情報 (Go 1.7以降): [https://go.dev/blog/go1.7-ssa](https://go.dev/blog/go1.7-ssa)
## 参考にした情報源リンク
* mattermost.com: [https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQECXVSzU5WTlSxD2gC-jNMF9O8ME4eeXWj7pXAUSOOFYyVyIwsgQfhHG2OTSmHS10u5Ug9J8lTH9XrJdG8FHlE8bO2Cq5Wk-7DDG7Fd-R7xok7_Q5UT12biDgD60oH6ajqx1IbMh_m_ox6MTuUkATWxp2svBqNsUKeqZJE5-aelwb5DRyl7Mccjj9JDfb5yXUKq](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQECXVSzU5WTlSxD2gC-jNMF9O8ME4eeXWj7pXAUSOOFYyVyIwsgQfhHG2OTSmHS10u5Ug9J8lTH9XrJdG8FHlE8bO2Cq5Wk-7DDG7Fd-R7xok7_Q5UT12biDgD60oH6ajqx1IbMh_m_ox6MTuUkATWxp2svBqNsUKeqZJE5-aelwb5DRyl7Mccjj9JDfb5yXUKq)
* thegreenplace.net: [https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFClY54-AA4u6-IH2Qlx4CdrdwJrDRpodgFv9p_OqYEVcFXnGyK_2DaXuI7nFXc-3I9AW2-NlmcSqyrkclJwwbDgLN1LinJlBu2MHdiaZWxa6tRS6aGi7VDfAmvk80JKlqBj2VXI23iF0hJjoyPre1beAnh5tAUyBMXwtS18QMI5flc1UdzNk8-O1gVDtTkIa2f-5hCFD75DA==](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFClY54-AA4u6-IH2Qlx4CdrdwJrDRpodgFv9p_OqYEVcFXnGyK_2DaXuI7nFXc-3I9AW2-NlmcSqyrkclJwwbDgLN1LinJlBu2MHdiaZWxa6tRS6aGi7VDfAm3Amvk80JKlqBj2VXI23iF0hJjoyPre1beAnh5tAUyBMXwtS18QMI5flc1UdzNk8-O1gVDtTkIa2f-5hCFD75DA==)
* reddit.com: [https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGE7g97nXLoLWg_ACoZnOhlXo-dv1n5pNjPQHiS2PdbVXICV6900Lw6T2_rji4i0mACYqoXF_EE8UujqN0BNe-S0UHG_n4MvwSrJMGwb3F3hBaJEa9XCygB0Hdwo-03plwQOGOxlQpmvBwx1grgkVDoQIncGJlAbYTDu1-wKfqkQuQ6eDLfJSU=](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGE7g97nXLoLWg_ACoZnOhlXo-dv1n5pNjPQHiS2PdbVXICV6900Lw6T2_rji4i0mACYqoXF_EE8UujqN0BNe-S0UHG_n4MvwSrJMGwb3F3hBaJEa9XCygB0Hdwo-03plwQOGOxlQpmvBwx1grgkVDoQIncGJlAbYTDu1-wKfqkQuQ6eDLfJSU=)
* codingexplorations.com: [https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEMsor4_ZJvSOlnwcU4cJiCJFISMMktzziyMoW_YqNAbWv65qUzvFNafThaB55JH3MHoZrJqI60scilvr6b-oWMzSGVeASxWUA5btcqff9118AgFlCv3le6CHo-DJiAlpb1KERU4h0-WBdd7ooRN2iJtyM_lXeuis6iJAEBffZReTI4PIiK](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEMsor4_ZJvSOlnwcU4cJiCJFISMMktzziyMoW_YqNAbWv65qUzvFNafThaB55JH3MHoZrJqI60scilvr6b-oWMzSGVeASxWUA5btcqff9118AgFlCv3le6CHo-DJiAlpb1KERU4h0-WBdd7ooRN2iJtyM_lXeuis6iJAEBffZReTI4PIiK)
* go.dev (SSA): [https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH1p8HL4UhH0SA52DPpZ4GLq0H2wkpeA2VyAW_O7FiznJoi6xQHI3LJVj-3s-3oxcK5Hv1NSCRaHsm3r31teH6MyQ79Y0r75rMdZg5nbf0V9LjfNOFshem1pDZ-1wFAVKSnx_1f5hiqCgXyINGR](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQH1p8HL4UhH0SA52DPpZ4GLq0H2wkpeA2VyAW_O7FiznJoi6xQHI3LJVj-3s-3oxcK5Hv1NSCRaHsm3r31teH6MyQ79Y0r75rMdZg5nbf0V9LjfNOFshem1pDZ-1wFAVKSnx_1f5hiqCgXyINGR)
* quasilyte.dev: [https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF1xSiJSOgdDLn8ZKJy2nljW7kF942ilePJcG1HCgUv1gLmbpJ0MniWTkQnpAXgboP5ziFAn330OTsKH-mOkI8HK4qfVaGztnKHqFF_B5TIm7f1W0jOhl4IyuDgZ8ph5VB3FtbkO5Py_ekvLHQ=](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF1xSiJSOgdDLn8ZKJy2nljW7kF942ilePJcG1HCgUv1gLmbpJ0MniWTkQnpAXgboP5ziFAn330OTsKH-mOkI8HK4qfVaGztnKHqFF_B5TIm7f1W0jOhl4IyuDgZ8ph5VB3FtbkO5Py_ekvLHQ=)
* go.dev (golang.org/x/tools/go/ssa): [https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFLAzrQYfCFqp6ddRMDwcpAo6S6333n9vff_Pwykd0-6mjWZJZERuggXSrNh36fuSSeCYyrBJkyy3XZSGkgScrB8yNl2mTlxARPb1ZqjTlaeftqRDmSXfssVdiHHnj5eRyxqYcHvxla](https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFLAzrQYfCFqp6ddRMDwcpAo6S6333n9vff_Pwykd0-6mjWZJZERuggXSrNh36fuSSeCYyrBJkyy3XZSGkgScrB8yNl2mTlxARPb1ZqjTlaeftqRDmSXfssVdiHHnj5eRyxqYcHvxla)