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

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

このコミットは、Go言語の実験的なSSA (Static Single Assignment) パッケージにおいて、defer ステートメントと名前付き戻り値のセマンティクスを正確に実装するための重要な変更を導入しています。具体的には、遅延実行されるプロシージャ呼び出し(defer)の実行を明示的に制御する新しいSSA命令 RunDefers を導入し、それによって名前付き戻り値の評価、deferの実行、そして最終的な戻り値の形成という、Go言語の仕様に合致した厳密な順序を保証します。

コミット

commit 5a09f1b3be354ebb69a9124076236cb7aa83edc9
Author: Alan Donovan <adonovan@google.com>
Date:   Wed Feb 27 10:35:23 2013 -0500

    exp/ssa: make invokation of deferred procedure calls explicit.
    
    The correct semantics of named result parameters and deferred
    procedures cannot be implemented with the existing Ret
    instruction alone, since the required sequence is:
    (1) evaluate return operands and parallel-assign them to
        named result parameters
    (2) invoke deferred procedures
    (3) load named result parameters to form result tuple.
    
    We introduce a new 'rundefers' instruction that explicitly
    invokes the deferred procedure calls, and we generate code
    that follows the sequence above.
    
    Most functions do not use deferred procedures but this cannot
    be known in a single pass.  So, we add an optimisation to
    eliminate redundant 'rundefers'; it is piggybacked on the
    existing pass done for "lifting".
    
    Added tests.
    
    R=gri
    CC=golang-dev
    https://golang.org/cl/7411043

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

https://github.com/golang/go/commit/5a09f1b3be354ebb69a9124076236cb7aa83edc9

元コミット内容

このコミットは、Go言語の実験的なSSA (Static Single Assignment) パッケージにおいて、遅延実行されるプロシージャ呼び出し(defer)のセマンティクスを明示的に扱うための変更です。既存のRet(戻り値)命令だけでは、名前付き戻り値とdeferの正しいセマンティクスを実装できないという問題がありました。Go言語の仕様では、関数が戻る際に以下の厳密な順序が求められます。

  1. 戻り値のオペランドを評価し、それらを名前付き戻り値パラメータに並行代入する。
  2. 遅延実行されるプロシージャ(defer関数)を呼び出す。
  3. 名前付き戻り値パラメータをロードして、最終的な戻り値のタプルを形成する。

この問題を解決するため、新しいSSA命令であるRunDefersが導入されました。この命令は、遅延実行されるプロシージャ呼び出しを明示的に実行します。これにより、上記の正しい順序でコードが生成されるようになります。

また、ほとんどの関数はdeferを使用しないため、不要なRunDefers命令を削除する最適化も追加されました。この最適化は、既存の"lifting"パスに便乗して実装されています。変更の正当性を確認するために、関連するテストも追加されています。

変更の背景

Go言語のdeferステートメントは、関数がリターンする直前、またはパニックが発生した際に実行される関数を登録する強力な機能です。また、Go言語は名前付き戻り値(Named Result Parameters, NRPs)をサポートしており、これにより戻り値に変数を割り当て、関数本体内でその変数を操作し、returnステートメントで明示的に値を指定せずに戻すことができます。

これらの機能は非常に便利ですが、その実行順序には厳密なセマンティクスがあります。特に、名前付き戻り値が使用され、かつdefer関数が戻り値を変更する可能性がある場合、その順序は非常に重要になります。Go言語の仕様では、関数が戻る際には、まず戻り値が名前付き戻り値変数に代入され、その後にdefer関数が実行され、最後に名前付き戻り値変数の最終的な値が関数の戻り値として使用される、という順序が定められています。

従来のSSA中間表現では、Ret命令が単一の操作として戻り値を処理していました。しかし、この単一のRet命令では、名前付き戻り値への代入、deferの実行、そして名前付き戻り値からの最終的な値のロードという3つの異なるステップを、正しい順序で表現することが困難でした。このため、deferが名前付き戻り値を変更するような複雑なケースで、コンパイラが生成するコードがGo言語のセマンティクスに違反する可能性がありました。

このコミットは、このセマンティクス上のギャップを埋め、GoコンパイラのSSAバックエンドがdeferと名前付き戻り値の相互作用を正確にモデル化できるようにすることを目的としています。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念とコンパイラのSSAに関する基本的な知識が必要です。

1. Go言語の defer ステートメント

deferステートメントは、そのステートメントを含む関数がリターンする直前、またはパニックが発生した際に、指定された関数呼び出しを遅延実行するために使用されます。deferされた関数はLIFO(後入れ先出し)順で実行されます。

func example() (result int) {
    result = 0
    defer func() {
        result++ // defer関数内で名前付き戻り値を変更できる
    }()
    return 10 // ここで result は 10 になるが、defer実行後に 11 になる
}

重要なのは、defer関数が実行される時点では、すでに戻り値が名前付き戻り値変数に代入されているため、defer関数内でこれらの変数を読み書きできる点です。

2. Go言語の名前付き戻り値 (Named Result Parameters)

Go言語では、関数の戻り値に変数を命名することができます。これにより、関数本体内でこれらの変数に値を代入し、returnステートメントで明示的に値を指定せずに戻すことができます。

func calculate(x, y int) (sum int, diff int) {
    sum = x + y
    diff = x - y
    return // sum と diff の現在の値が戻り値となる
}

returnステートメントが実行される際、まず名前付き戻り値変数に値が代入され、その後defer関数が実行され、最後に名前付き戻り値変数の最終的な値が関数の戻り値として使用されます。

3. SSA (Static Single Assignment) 形式

SSAは、コンパイラの中間表現(IR)の一種です。SSA形式では、各変数が一度だけ代入されるという特性を持ちます。これにより、データフロー解析や最適化が容易になります。

  • 命令 (Instruction): SSA形式における基本的な操作単位。例えば、Add, Load, Store, Call, Retなどがあります。
  • 値 (Value): 命令の結果として生成されるデータ。SSAでは、各値は一度だけ定義されます。
  • 基本ブロック (Basic Block): 制御フローグラフのノード。単一の入り口と単一の出口を持ち、内部に分岐を含まない命令のシーケンスです。
  • Phi 関数: 複数の制御フローパスが合流する点で、異なるパスから来る同じ論理的な変数に異なるSSA値が割り当てられている場合に、それらを結合するために使用されます。

このコミットでは、新しいSSA命令RunDefersが導入され、既存のRet命令のセマンティクスが変更されています。これは、SSA形式における命令の粒度とセマンティクスを調整することで、Go言語の複雑な実行モデルを正確に表現しようとするものです。

4. init 関数と初期化ブロック

Go言語のinit関数は、パッケージが初期化される際に自動的に実行されます。複数のinit関数がある場合、それらは定義された順序で実行されます。コンパイラは、これらのinit関数やトップレベルの変数初期化を単一の大きな初期化関数に結合することがあります。この結合された関数内でもdeferが正しく動作する必要があります。

技術的詳細

このコミットの核心は、Go言語のdeferと名前付き戻り値の正確なセマンティクスをSSA中間表現で表現するために、新しいSSA命令RunDefersを導入し、既存のRet命令の役割を再定義した点にあります。

RunDefers 命令の導入

  • 目的: RunDefers命令は、現在の関数スコープで登録されているすべての遅延実行関数(deferされた関数)を、LIFO順に実行することを明示的に指示します。
  • 配置: RunDefers命令は、関数の戻りパス(returnステートメントの直前や、関数の終端に到達した場合)に挿入されます。これにより、名前付き戻り値への代入が完了した後、かつ最終的な戻り値が形成される前にdeferが実行されることが保証されます。
  • 多重挿入の許容: RunDefers命令は、単一の制御フローパス中に複数回出現することが許容されます。これは、特に複数のinitブロックが結合された関数のようなケースで有用です。各initブロックの終端でdeferが実行されるべきですが、これらが単一の関数に結合されると、その結合された関数の終端で一度だけRunDefersが実行されるだけでは不十分になるためです。

Ret 命令のセマンティクス変更

  • 従来のRet命令は、戻り値の評価、名前付き戻り値への代入、deferの実行、最終的な戻り値の形成といった一連の操作を暗黙的に含んでいるか、あるいはその順序が不明確でした。
  • この変更により、Ret命令は純粋に「関数から戻る」という制御フローの役割に特化されます。戻り値の評価と名前付き戻り値への代入はRet命令の前に明示的に行われ、deferの実行は新しいRunDefers命令によって明示的に制御されます。

名前付き戻り値の処理フローの変更

コミットメッセージに記載されているように、名前付き戻り値とdeferの正しいセマンティクスを実現するために、以下の3ステップがSSAコード生成時に厳密に適用されます。

  1. 戻り値の評価と名前付き戻り値への並行代入: returnステートメントで指定された値(または名前付き戻り値が暗黙的に持つ値)が評価され、関数の名前付き戻り値パラメータ(f.namedResults)に並行して代入されます。これはemitStore命令などによって行われます。
  2. RunDefers 命令の挿入: このステップの直後にRunDefers命令が挿入され、登録されたdefer関数が実行されます。
  3. 名前付き戻り値のロードと結果タプルの形成: RunDefersの実行後、名前付き戻り値パラメータの最終的な値がロードされ(emitLoad)、それらが関数の最終的な戻り値タプルとしてRet命令に渡されます。

この明確な分離と順序付けにより、defer関数が名前付き戻り値を変更するようなケースでも、Go言語の仕様に完全に準拠したコードが生成されるようになります。

最適化: 不要な RunDefers の除去

  • ほとんどの関数はdeferを使用しないため、そのような関数にRunDefers命令を無条件に挿入すると、不要なオーバーヘッドが生じます。
  • このコミットでは、既存の"lifting"パス(SSA最適化の一種で、変数の割り当てを最適化する)に便乗して、不要なRunDefers命令を削除する最適化が追加されました。
  • "lifting"パス中に、関数内にDefer命令が存在するかどうかを追跡します。もしDefer命令が一つも存在しない関数であれば、その関数内のすべてのRunDefers命令は冗長であると判断され、削除されます。これにより、deferを使用しない関数のパフォーマンスが不必要に低下するのを防ぎます。

影響を受けるファイルと変更点

  • src/pkg/exp/ssa/builder.go:
    • buildReturn関数内で、RunDefers命令の挿入ロジックが追加されました。名前付き戻り値の処理が、defer実行の前後に分割されています。
    • 関数の終端に到達した場合の暗黙的なreturnや、initブロックの終端にもRunDefersが挿入されるようになりました。
  • src/pkg/exp/ssa/ssa.go:
    • 新しいSSA命令RunDefersの構造体が定義されました。
    • Function構造体のresultsフィールドがnamedResultsにリネームされ、名前付き戻り値の管理がより明確になりました。
  • src/pkg/exp/ssa/interp/interp.go:
    • SSAインタープリタにRunDefers命令を処理するためのロジックが追加されました。rundefersというヘルパー関数が導入され、fr.defersスタックを逆順に実行するようになりました。
    • callSSA関数内のdefer実行ロジックがfr.rundefers()の呼び出しに置き換えられました。
  • src/pkg/exp/ssa/lift.go:
    • lift関数内で、Defer命令の存在を追跡するusesDeferフラグが追加されました。
    • RunDefers命令の数をカウントするb.rundefersフィールドが追加されました。
    • usesDeferfalseの場合(つまり、関数内にDefer命令がない場合)、RunDefers命令を削除するロジックが追加されました。
  • src/pkg/exp/ssa/func.go:
    • f.resultsf.namedResultsにリネームされ、関連する初期化とクリアのロジックが更新されました。
  • src/pkg/exp/ssa/print.go:
    • RunDefers命令の文字列表現("rundefers")が追加されました。
  • src/pkg/exp/ssa/sanity.go:
    • SSA命令の健全性チェックにRunDefersが追加されました。
  • src/pkg/exp/ssa/interp/testdata/coverage.go:
    • deferが名前付き戻り値を変更するケースや、init関数内でのdeferの実行順序に関する新しいテストケースが追加されました。これにより、変更がGo言語のセマンティクスに正しく準拠していることが検証されます。

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

このコミットの主要な変更は、src/pkg/exp/ssa/builder.goにおけるRunDefers命令の挿入ロジックと、src/pkg/exp/ssa/ssa.goにおけるRunDefers命令の定義、そしてsrc/pkg/exp/ssa/lift.goにおける最適化ロジックです。

src/pkg/exp/ssa/builder.go (抜粋)

--- a/src/pkg/exp/ssa/builder.go
+++ b/src/pkg/exp/ssa/builder.go
@@ -2276,40 +2276,45 @@ start:
 				block = t._break
 			}
 		}
+		// Run function calls deferred in this init
+		// block when explicitly returning from it.
+		fn.emit(new(RunDefers))
 		emitJump(fn, block)
 		fn.currentBlock = fn.newBasicBlock("unreachable")
 		return
 	}
-		var results []Value
-		// Per the spec, there are three distinct cases of return.
-		switch {
-		case len(s.Results) == 0:
-			// Return with no arguments.
-			// Prior assigns to named result params are
-			// reloaded into results tuple.
-			// A void function is a degenerate case of this.
-			for _, r := range fn.results {
-				results = append(results, emitLoad(fn, r))
-			}

-		case len(s.Results) == 1 && len(fn.Signature.Results) > 1:
+		var results []Value
+		if len(s.Results) == 1 && len(fn.Signature.Results) > 1 {
 			// Return of one expression in a multi-valued function.
 			tuple := b.exprN(fn, s.Results[0])
 			for i, v := range tuple.Type().(*types.Result).Values {
 				results = append(results, emitExtract(fn, tuple, i, v.Type))
 			}
-
-		default:
-			// Return one or more single-valued expressions.
-			// These become the scalar or tuple result.
-			for _, r := range s.Results {
-				results = append(results, b.expr(fn, r))
+		} else {
+			// 1:1 return, or no-arg return in non-void function.
+			for i, r := range s.Results {
+				v := emitConv(fn, b.expr(fn, r), fn.Signature.Results[i].Type)
+				results = append(results, v)
 			}
 		}
-		// Perform implicit conversions.
-		for i := range results {
-			results[i] = emitConv(fn, results[i], fn.Signature.Results[i].Type)
+		if fn.namedResults != nil {
+			// Function has named result parameters (NRPs).
+			// Perform parallel assignment of return operands to NRPs.
+			for i, r := range results {
+				emitStore(fn, fn.namedResults[i], r)
+			}
+		}
+		// Run function calls deferred in this
+		// function when explicitly returning from it.
+		fn.emit(new(RunDefers))
+		if fn.namedResults != nil {
+			// Reload NRPs to form the result tuple.
+			results = results[:0]
+			for _, r := range fn.namedResults {
+				results = append(results, emitLoad(fn, r))
+			}
 		}
 		fn.emit(&Ret{Results: results})
 		fn.currentBlock = fn.newBasicBlock("unreachable")
@@ -2410,7 +2415,9 @@ func (b *Builder) buildFunction(fn *Function) {
 	fn.start(b.idents)
 	b.stmt(fn, fn.syntax.body)
 	if cb := fn.currentBlock; cb != nil && (cb == fn.Blocks[0] || cb.Preds != nil) {
-\t\t// We fell off the end: an implicit no-arg return statement.
+\t\t// Run function calls deferred in this function when
+\t\t// falling off the end of the body block.
+\t\tfn.emit(new(RunDefers))
 \t\tfn.emit(new(Ret))\n \t}\n \tfn.finish()\n@@ -2686,6 +2693,9 @@ func (b *Builder) buildDecl(pkg *Package, decl ast.Decl) {\n \t\t\t\t_break: next,\n \t\t\t}\n \t\t\tb.stmt(init, decl.Body)\n+\t\t\t// Run function calls deferred in this init\n+\t\t\t// block when falling off the end of the block.\n+\t\t\tinit.emit(new(RunDefers))\n \t\t\temitJump(init, next)\n \t\t\tinit.targets = init.targets.tail\n \t\t\tinit.currentBlock = next\n@@ -2792,6 +2802,7 @@ func (b *Builder) BuildPackage(p *Package) {\n \t// Finish up.\n \temitJump(init, done)\n \tinit.currentBlock = done\n+\tinit.emit(new(RunDefers))\n \tinit.emit(new(Ret))\n \tinit.finish()\n }\n```

### `src/pkg/exp/ssa/ssa.go` (抜粋)

```diff
--- a/src/pkg/exp/ssa/ssa.go
+++ b/src/pkg/exp/ssa/ssa.go
@@ -240,7 +240,7 @@ type Function struct {
 	// then cleared.
 	currentBlock *BasicBlock             // where to emit code
 	objects      map[types.Object]Value  // addresses of local variables
-	results      []*Alloc                // tuple of named results
+	namedResults []*Alloc                // tuple of named results
 	syntax       *funcSyntax             // abstract syntax trees for Go source functions
 	targets      *targets                // linked stack of branch targets
 	lblocks      map[*ast.Object]*lblock // labelled blocks
@@ -270,6 +270,7 @@ type BasicBlock struct {
 	succs2       [2]*BasicBlock // initial space for Succs.
 	dom          *domNode       // node in dominator tree; optional.
 	gaps         int            // number of nil Instrs (transient).
+	rundefers    int            // number of rundefers (transient)
 }
 
 // Pure values ----------------------------------------
@@ -817,9 +818,7 @@ type If struct {
 // Ret returns values and control back to the calling function.
 //
 // len(Results) is always equal to the number of results in the
-// function's signature.  A source-level 'return' statement with no
-// operands in a multiple-return value function is desugared to make
-// the results explicit.
+// function's signature.
 //
 // If len(Results) > 1, Ret returns a tuple value with the specified
 // components which the caller must access using Extract instructions.
@@ -827,9 +826,6 @@ type If struct {
 // There is no instruction to return a ready-made tuple like those
 // returned by a "value,ok"-mode TypeAssert, Lookup or UnOp(ARROW) or
 // a tail-call to a function with multiple result parameters.
-// TODO(adonovan): consider defining one; but: dis- and re-assembling
-// the tuple is unavoidable if assignability conversions are required
-// on the components.
 //
 // Ret must be the last instruction of its containing BasicBlock.
 // Such a block has no successors.
@@ -843,6 +839,20 @@ type Ret struct {
 	Results []Value
 }
 
+// RunDefers pops and invokes the entire stack of procedure calls
+// pushed by Defer instructions in this function.
+//
+// It is legal to encounter multiple 'rundefers' instructions in a
+// single control-flow path through a function; this is useful in
+// the combined init() function, for example.
+//
+// Example printed form:
+//	rundefers
+//
+type RunDefers struct {
+	anInstruction
+}
+
 // Panic initiates a panic with value X.
 //
 // A Panic instruction must be the last instruction of its containing
@@ -875,8 +885,7 @@ type Go struct {
 }
 
 // Defer pushes the specified call onto a stack of functions
-// to be called immediately prior to returning from the
-// current function.
+// to be called by a RunDefers instruction or by a panic.
 //
 // See CallCommon for generic function call documentation.
 //
@@ -1146,6 +1155,7 @@ func (*Panic) ImplementsInstruction()           {}\n func (*Phi) ImplementsInstruction()             {}\n func (*Range) ImplementsInstruction()           {}\n func (*Ret) ImplementsInstruction()             {}\n+func (*RunDefers) ImplementsInstruction()       {}\n func (*Select) ImplementsInstruction()          {}\n func (*Send) ImplementsInstruction()            {}\n func (*Slice) ImplementsInstruction()           {}\n@@ -1267,6 +1277,10 @@ func (s *Ret) Operands(rands []*Value) []*Value {\n \treturn rands\n }\n \n+func (*RunDefers) Operands(rands []*Value) []*Value {\n+\treturn rands\n+}\n+\n func (v *Select) Operands(rands []*Value) []*Value {\n \tfor i := range v.States {\n \t\trands = append(rands, &v.States[i].Chan, &v.States[i].Send)\n```

### `src/pkg/exp/ssa/lift.go` (抜粋)

```diff
--- a/src/pkg/exp/ssa/lift.go
+++ b/src/pkg/exp/ssa/lift.go
@@ -154,22 +154,33 @@ func lift(fn *Function) {
 	// concatenation of all non-dead newPhis and non-nil Instrs
 	// for the block, reusing the original array if space permits.
 
+\t// While we're here, we also eliminate 'rundefers'
+\t// instructions in functions that contain no 'defer'
+\t// instructions.
+\tusesDefer := false
+\n 	// Determine which allocs we can lift and number them densely.\n 	// The renaming phase uses this numbering for compact maps.\n 	numAllocs := 0\n 	for _, b := range fn.Blocks {\n \t\tb.gaps = 0
+\t\tb.rundefers = 0
 \t\tfor i, instr := range b.Instrs {\n-\t\t\tif alloc, ok := instr.(*Alloc); ok {\n-\t\t\t\tif liftAlloc(df, alloc, newPhis) {\n-\t\t\t\t\talloc.index = numAllocs
+\t\t\tswitch instr := instr.(type) {\n+\t\t\tcase *Alloc:\n+\t\t\t\tif liftAlloc(df, instr, newPhis) {\n+\t\t\t\t\tinstr.index = numAllocs
 \t\t\t\t\tnumAllocs++\n \t\t\t\t\t// Delete the alloc.\n \t\t\t\t\tb.Instrs[i] = nil
 \t\t\t\t\tb.gaps++
 \t\t\t\t} else {\n-\t\t\t\t\talloc.index = -1
+\t\t\t\t\tinstr.index = -1
 \t\t\t\t}\n+\t\t\tcase *Defer:\n+\t\t\t\tusesDefer = true\n+\t\t\tcase *RunDefers:\n+\t\t\t\tb.rundefers++
 \t\t\t}\n \t\t}\n \t}\n@@ -202,22 +213,33 @@ func lift(fn *Function) {\n \t\t}\n \t\tnps = nps[:j]\n 
-\t\tif j+b.gaps == 0 {\n-\t\t\tcontinue // fast path: no new phis and no gaps\n+\t\trundefersToKill := b.rundefers\n+\t\tif usesDefer {\n+\t\t\trundefersToKill = 0\n+\t\t}\n+\n+\t\tif j+b.gaps+rundefersToKill == 0 {\n+\t\t\tcontinue // fast path: no new phis or gaps\n \t\t}\n \n \t\t// Compact nps + non-nil Instrs into a new slice.\n \t\t// TODO(adonovan): opt: compact in situ if there is\n \t\t// sufficient space or slack in the slice.\n-\t\tdst := make([]Instruction, j+len(b.Instrs)-b.gaps)\n+\t\tdst := make([]Instruction, len(b.Instrs)+j-b.gaps-rundefersToKill)\n \t\tfor i, np := range nps {\n \t\t\tdst[i] = np.phi\n \t\t}\n \t\tfor _, instr := range b.Instrs {\n-\t\t\tif instr != nil {\n-\t\t\t\tdst[j] = instr\n-\t\t\t\tj++\n+\t\t\tif instr == nil {\n+\t\t\t\tcontinue\n \t\t\t}\n+\t\t\tif !usesDefer {\n+\t\t\t\tif _, ok := instr.(*RunDefers); ok {\n+\t\t\t\t\tcontinue\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tdst[j] = instr\n+\t\t\tj++\n \t\t}\n \t\tfor i, np := range nps {\n \t\t\tdst[i] = np.phi\n```

## コアとなるコードの解説

### `src/pkg/exp/ssa/builder.go` の変更

このファイルは、GoのAST(抽象構文木)からSSA中間表現を構築する役割を担っています。変更の核心は、`return`ステートメントや関数の暗黙的な終了点、`init`ブロックの終了点に`RunDefers`命令を挿入するロジックです。

*   **`buildReturn` 関数内**:
    *   以前は、戻り値の処理が単一の`switch`文で行われていましたが、名前付き戻り値の処理と`defer`の実行が明確に分離されました。
    *   `if fn.namedResults != nil`ブロックが追加され、名前付き戻り値が存在する場合の処理が定義されています。
        1.  まず、`return`ステートメントで指定された値が評価され、`results`スライスに格納されます。
        2.  次に、`for i, r := range results { emitStore(fn, fn.namedResults[i], r) }`によって、これらの値が名前付き戻り値パラメータ(`fn.namedResults`)に並行代入されます。
        3.  **`fn.emit(new(RunDefers))`**: ここで新しい`RunDefers`命令が挿入されます。これにより、名前付き戻り値への代入が完了した直後に`defer`関数が実行されることが保証されます。
        4.  `if fn.namedResults != nil`ブロックの後半で、`results = results[:0]; for _, r := range fn.namedResults { results = append(results, emitLoad(fn, r)) }`というコードが追加されています。これは、`defer`関数が名前付き戻り値を変更する可能性があるため、`RunDefers`実行後に名前付き戻り値パラメータから最終的な値を再ロードして、`Ret`命令に渡すためのものです。
*   **関数の暗黙的な終了点**: `buildFunction`関数内で、関数の本体ブロックの終端に到達した場合(明示的な`return`がない場合)にも、`fn.emit(new(RunDefers))`が挿入されるようになりました。これにより、暗黙的な`return`でも`defer`が正しく実行されます。
*   **`init` ブロックの終了点**: `buildDecl`関数や`BuildPackage`関数内で、`init`ブロックの終端にも`init.emit(new(RunDefers))`が挿入されています。これは、`init`関数内でも`defer`が使用される可能性があり、その場合も正しく実行される必要があるためです。

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

このファイルはSSA中間表現の基本的なデータ構造と命令を定義しています。

*   **`Function` 構造体**:
    *   `results []*Alloc`フィールドが`namedResults []*Alloc`にリネームされました。これは、このフィールドが名前付き戻り値パラメータの割り当て(`Alloc`)を保持することをより明確に示しています。
*   **`BasicBlock` 構造体**:
    *   `rundefers int`フィールドが追加されました。これは、この基本ブロック内に存在する`RunDefers`命令の数を一時的に追跡するために使用されます。最適化パス(`lift.go`)で利用されます。
*   **`RunDefers` 構造体**:
    *   新しいSSA命令`RunDefers`が定義されました。これは`anInstruction`を埋め込んでおり、SSA命令としての基本的な振る舞いを継承します。この構造体自体はフィールドを持ちません。これは、`RunDefers`が単に「`defer`スタックを実行する」というアクションを表すためです。
    *   関連するメソッド(`ImplementsInstruction()`, `Operands()`)も追加されています。

### `src/pkg/exp/ssa/lift.go` の変更

このファイルは、SSA中間表現に対する最適化パス("lifting"パス)を実装しています。このコミットでは、このパスに`RunDefers`命令の最適化ロジックが追加されました。

*   **`usesDefer` フラグ**:
    *   `lift`関数の冒頭で`usesDefer := false`が宣言されます。
    *   基本ブロック内の命令を走査するループ内で、`instr := instr.(type)`の`switch`文に`case *Defer:`が追加され、`Defer`命令が見つかった場合に`usesDefer`が`true`に設定されます。これにより、関数全体で`Defer`命令が使用されているかどうかが追跡されます。
*   **`b.rundefers` カウント**:
    *   `case *RunDefers:`が追加され、`RunDefers`命令が見つかるたびに`b.rundefers++`でカウントされます。
*   **`RunDefers` 命令の削除ロジック**:
    *   基本ブロックの命令を新しいスライス`dst`にコンパクト化する際に、`if !usesDefer { if _, ok := instr.(*RunDefers); ok { continue } }`という条件が追加されました。
    *   このロジックは、「もし関数全体で`Defer`命令が一つも使われていない(`usesDefer`が`false`)ならば、この`RunDefers`命令はスキップ(削除)する」というものです。これにより、`defer`を使用しない関数から不要な`RunDefers`命令が取り除かれ、生成されるコードの効率が向上します。

これらの変更により、Go言語の`defer`と名前付き戻り値の複雑な相互作用がSSA中間表現で正確にモデル化され、コンパイラがGo言語の仕様に完全に準拠したコードを生成できるようになりました。

## 関連リンク

*   Go言語の`defer`ステートメントに関する公式ドキュメント: [https://go.dev/blog/defer-panic-and-recover](https://go.dev/blog/defer-panic-and-recover)
*   Go言語の関数と戻り値に関する公式ドキュメント: [https://go.dev/ref/spec#Function_declarations](https://go.dev/ref/spec#Function_declarations)
*   このコミットのGo Gerritレビューページ: [https://golang.org/cl/7411043](https://golang.org/cl/7411043)

## 参考にした情報源リンク

*   Go言語の公式ドキュメント
*   Go言語のソースコード(特に`src/cmd/compile/internal/ssa`パッケージ)
*   SSA形式に関する一般的な情報源(コンパイラ最適化の教科書など)