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

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

コミット

commit aa5aaabb0d18d21d97e9c98d7dfde4899a498335
Author: Alan Donovan <adonovan@google.com>
Date:   Thu Feb 21 12:48:38 2013 -0500

    exp/ssa/interp: (#6 of 5): test interpretation of SSA form of $GOROOT/test/*.go.
    
    The interpreter's os.Exit now triggers a special panic rather
    than kill the test process.  (It's semantically dubious, since
    it will run deferred routines.)  Interpret now returns its
    exit code rather than calling os.Exit.
    
    Also:
    - disabled parts of a few $GOROOT/tests via os.Getenv("GOSSAINTERP").
    - remove unnecessary 'slots' param to external functions; they
      are never closures.
    
    Most of the tests are disabled until go/types supports shifts.
    They can be reenabled if you patch this workaround:
    https://golang.org/cl/7312068
    
    R=iant, bradfitz
    CC=golang-dev, gri
    https://golang.org/cl/7313062

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

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

元コミット内容

このコミットは、Go言語の実験的なSSA(Static Single Assignment)形式インタープリタである exp/ssa/interp のテストフレームワークと動作の改善を目的としています。主な変更点は以下の通りです。

  1. os.Exit の挙動変更: インタープリタ内で os.Exit が呼び出された際に、プロセスを終了させる代わりに特別なパニックを発生させるように変更されました。これにより、テストプロセスが終了することなく、インタープリタの実行を制御できるようになります。ただし、この変更は defer ルーチンが実行されるという点で、本来の os.Exit のセマンティクスとは異なる可能性があります。
  2. Interpret 関数の戻り値: Interpret 関数が終了コードを返すように変更され、os.Exit を直接呼び出す代わりに、呼び出し元が終了コードを処理できるようになりました。
  3. テストの条件付き無効化: $GOROOT/test ディレクトリ内のいくつかのテストが、環境変数 GOSSAINTERP の有無によって条件付きで無効化されるようになりました。これは、インタープリタがまだ完全にGo言語の全機能をサポートしていないため、特に go/types パッケージがシフト演算を正しくサポートするまでの間、一部のテストをスキップするための措置です。
  4. 外部関数の slots パラメータ削除: 外部関数(Goの組み込み関数や標準ライブラリ関数をインタープリタが呼び出すためのラッパー)から不要な slots パラメータが削除されました。これは、これらの関数がクロージャではないため、slots パラメータが不要であると判断されたためです。
  5. 新しいテストファイルの追加: interp_test.gotestdata/coverage.go が追加され、インタープリタのテストカバレッジが向上しました。

変更の背景

このコミットの背景には、Go言語のコンパイラツールチェーンにおけるSSA形式の導入と、そのSSA形式を解釈・実行するインタープリタの開発があります。

Go言語のコンパイラは、コードの最適化をより効率的に行うために、中間表現としてSSA形式を採用しています。exp/ssa パッケージは、GoプログラムのSSA形式を生成・操作するための実験的なツールを提供します。exp/ssa/interp は、このSSA形式のプログラムを実際に実行(解釈)するためのインタープリタであり、コンパイラの開発やデバッグ、あるいはGoプログラムの動作解析などに利用されます。

このコミットが行われた時点では、exp/ssa/interp はまだ開発途上にあり、Go言語の全てのセマンティクスを完全にエミュレートできるわけではありませんでした。特に、以下の課題がありました。

  • os.Exit のテストにおける問題: os.Exit が呼び出されると、テストプロセス全体が終了してしまい、インタープリタのテストが困難になるという問題がありました。テストフレームワーク内で os.Exit の挙動を制御し、テストの継続性を確保する必要がありました。
  • go/types のシフト演算の未サポート: go/types パッケージ(Goの型システムを扱うパッケージ)が、ビットシフト演算(<<, >>)を完全にサポートしていなかったため、シフト演算を含むGoプログラムをSSAインタープリタで正しく解釈できないという問題がありました。これにより、多くの既存のGoテストがインタープリタで実行できない状態でした。
  • 外部関数の効率化: 外部関数呼び出しの際に、不要なパラメータ(slots)が渡されており、コードの冗長性や潜在的な混乱を招いていました。

これらの課題に対処し、exp/ssa/interp の堅牢性とテストカバレッジを向上させることが、このコミットの主要な目的でした。

前提知識の解説

このコミットを理解するためには、以下の概念に関する前提知識が必要です。

  • SSA (Static Single Assignment) 形式:
    • SSA形式は、コンパイラ最適化で広く用いられるプログラムの中間表現です。
    • SSA形式では、各変数が一度だけ代入される(静的に単一代入される)という特性を持ちます。これにより、データフロー解析や最適化が容易になります。
    • 例えば、x = a + b; x = x * 2; というコードは、SSA形式では x1 = a + b; x2 = x1 * 2; のように変換され、各変数の定義と使用が明確になります。
    • Go言語のコンパイラは、内部的にSSA形式を使用してコードを最適化しています。
  • Go言語の exp/ssa パッケージ:
    • exp/ssa は、GoプログラムのSSA形式を生成・操作するための実験的なパッケージです。
    • このパッケージは、GoのソースコードからSSA形式のグラフを構築し、そのグラフを分析・変換するためのAPIを提供します。
    • コンパイラの開発者や、Goプログラムの静的解析ツールを開発する際に利用されます。
  • Go言語の exp/ssa/interp パッケージ:
    • exp/ssa/interp は、exp/ssa パッケージによって生成されたSSA形式のGoプログラムを解釈・実行するインタープリタです。
    • 実際のCPU上で実行するのではなく、SSA形式の命令をソフトウェア的にシミュレートすることでプログラムを実行します。
    • デバッグ、プロファイリング、あるいは特定のコードパスの動作検証などに利用されます。
  • Go言語の os.Exit:
    • os.Exit(code int) は、Goプログラムを終了させるための関数です。
    • この関数が呼び出されると、プログラムは指定された終了コードで直ちに終了します。
    • 重要な点として、os.Exitdefer ステートメントで登録された関数を実行しません。これは、通常のパニックやエラー処理とは異なる挙動です。
  • Go言語の panicrecover:
    • panic は、Goプログラムで回復不能なエラーが発生した際に、プログラムの実行を中断するために使用されます。
    • panic が発生すると、現在のゴルーチンの実行が停止し、defer ステートメントで登録された関数が順次実行されます。
    • recover は、panic から回復するために使用される組み込み関数です。defer 関数内で recover を呼び出すことで、パニックの値を捕捉し、プログラムの実行を継続させることができます。
  • Go言語の reflect パッケージ:
    • reflect パッケージは、実行時にGoプログラムの型情報を検査したり、変数の値を操作したりするための機能を提供します。
    • リフレクションは、汎用的なデータ処理や、Go言語の組み込み機能では実現が難しい動的な操作を行う際に利用されます。
  • Go言語の go/types パッケージ:
    • go/types パッケージは、Go言語の型システムをプログラム的に表現・操作するためのパッケージです。
    • Goコンパイラや静的解析ツールが、Goソースコードの型チェックを行う際に利用します。
    • このコミットの時点では、go/types がビットシフト演算の型推論やチェックに関して一部の制限を抱えていたことが示唆されています。
  • Go CL (Change List):
    • Goプロジェクトでは、コード変更は「Change List」(CL)として提出され、レビューを経てマージされます。
    • golang.org/cl/XXXXXXX の形式で参照されるURLは、特定のCLへのリンクです。

技術的詳細

このコミットの技術的詳細は、exp/ssa/interp の内部実装と、Go言語のランタイム挙動のシミュレーションに関するものです。

  1. os.Exit のパニック化:
    • 従来の os.Exit は、インタープリタの実行を強制的に終了させていました。これはテストの自動化において問題となります。
    • このコミットでは、src/pkg/exp/ssa/interp/external.go 内の ext۰syscall۰Exit 関数が変更され、syscall.Exit を直接呼び出す代わりに panic(exitPanic(args[0].(int))) を実行するようにしました。
    • exitPanic は、interp.go で定義されたカスタム型 type exitPanic int です。
    • Interpret 関数の defer ブロック内で recover() を使用し、この exitPanic 型のパニックを捕捉します。捕捉された場合、パニックの値(終了コード)を Interpret 関数の戻り値として返します。
    • これにより、インタープリタの実行は中断されますが、テストフレームワークはパニックを捕捉し、終了コードを検査できるようになります。ただし、このアプローチは defer ルーチンを実行してしまうため、厳密な os.Exit のセマンティクスとは異なります。
  2. Interpret 関数の戻り値変更:
    • func Interpret(...) のシグネチャが func Interpret(...) (exitCode int) に変更されました。
    • これにより、インタープリタの実行結果(正常終了か、パニックによる終了か、os.Exit による終了か)を数値で表現し、呼び出し元(テストコードなど)がその結果に基づいて適切な処理を行えるようになりました。
    • 正常終了の場合は 0os.Exit の場合はその引数、その他のパニックの場合は 2 が返されます。
  3. 外部関数の slots パラメータ削除:
    • src/pkg/exp/ssa/interp/external.go において、externalFn 型の定義が type externalFn func(fn *ssa.Function, args []value, slots []value) value から type externalFn func(fn *ssa.Function, args []value) value に変更されました。
    • これに伴い、ext۰math۰Float64frombits などの全ての外部関数ラッパーのシグネチャから slots []value パラメータが削除されました。
    • slots パラメータは、GoのSSA形式においてクロージャのキャプチャ変数(フリー変数)を表現するために使用されることがありますが、外部関数はGoのランタイムによって提供される組み込み関数であり、クロージャではないため、このパラメータは不要でした。この変更により、コードの簡潔性と効率性が向上しました。
  4. GOSSAINTERP 環境変数によるテスト制御:
    • test/blank.go, test/cmp.go, test/const.go, test/recover.go などの既存のGoテストファイルに、os.Getenv("GOSSAINTERP") == "" という条件が追加されました。
    • これは、exp/ssa/interp がまだ完全にGoの全ての機能をサポートしていない(特に unsafe.Pointer の扱い、recover() の挙動、定数畳み込みの精度など)ため、インタープリタで実行する際にはこれらのテストの一部をスキップするためのものです。
    • interp.goInterpret 関数内で、os パッケージの初期化時に envs = append(envs, "GOSSAINTERP=1") が追加され、インタープリタ実行時にはこの環境変数が設定されるようになっています。これにより、インタープリタが実行されていることをテストコードが検知し、適切な挙動を選択できます。
  5. go/types のシフト演算問題:
    • コミットメッセージで言及されている go/types のシフト演算問題は、https://golang.org/cl/7312068 で示唆されています。これは、go/types がシフト演算の型チェックや定数評価を正しく行えないバグまたは制限があったことを示しています。
    • この問題のため、interp_test.go で追加された多くの $GOROOT/test ファイルがコメントアウトされています。これは、インタープリタが依存する go/types がシフト演算を正しく扱えないため、これらのテストが失敗する可能性があったためです。

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

このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。

  • src/pkg/exp/ssa/interp/external.go:
    • externalFn 型のシグネチャから slots []value パラメータが削除されました。
    • ext۰syscall۰Exit 関数が syscall.Exit を直接呼び出す代わりに panic(exitPanic(args[0].(int))) を実行するように変更されました。
    • ext۰reflect۰Value۰String が追加されました。
  • src/pkg/exp/ssa/interp/interp.go:
    • Interpret 関数のシグネチャが func Interpret(...) から func Interpret(...) (exitCode int) に変更され、戻り値として終了コードを返すようになりました。
    • Interpret 関数内の defer ブロックが拡張され、exitPanic 型のパニックを捕捉し、その値を exitCode として設定するロジックが追加されました。
    • os パッケージの初期化時に GOSSAINTERP=1 環境変数を設定する処理が追加されました。
    • sizeof_C_MStats の値が 3450 から 3696 に更新されました。
    • callSSA 関数で外部関数を呼び出す際に、slots パラメータが渡されなくなりました。
  • src/pkg/exp/ssa/interp/interp_test.go (新規ファイル):
    • TestInterp 関数が定義され、$GOROOT/test および exp/ssa/interp/testdata/ 内のGoプログラムをSSAインタープリタで実行するテストフレームワークが実装されました。
    • 多くのテストが go/types のシフト演算問題のためにコメントアウトされています。
  • src/pkg/exp/ssa/interp/reflect.go:
    • reflectTypesPackage という新しいパッケージ変数が追加されました。
    • makeNamedType 関数が reflectTypesPackage を使用するように変更されました。
    • ext۰reflect 系の関数のシグネチャから slots []value パラメータが削除されました。
    • initReflect 関数が reflectTypesPackage を使用するように変更されました。
  • test/blank.go, test/cmp.go, test/const.go, test/recover.go:
    • os.Getenv("GOSSAINTERP") == "" という条件分岐が追加され、exp/ssa/interp で実行される際に一部のテストロジック(特に unsafe.Pointer を使用する部分や recover() の挙動に依存する部分、定数畳み込みの精度に依存する部分)がスキップされるようになりました。

コアとなるコードの解説

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

// 変更前
type externalFn func(fn *ssa.Function, args []value, slots []value) value

// 変更後
type externalFn func(fn *ssa.Function, args []value) value

// 変更前 (例)
func ext۰math۰Float64frombits(fn *ssa.Function, args []value, slots []value) value {
	return math.Float64frombits(args[0].(uint64))
}

// 変更後 (例)
func ext۰math۰Float64frombits(fn *ssa.Function, args []value) value {
	return math.Float64frombits(args[0].(uint64))
}

// 変更前
func ext۰syscall۰Exit(fn *ssa.Function, args []value, slots []value) value {
	// We could emulate syscall.Syscall but it's more effort.
	syscall.Exit(args[0].(int))
	return nil
}

// 変更後
func ext۰syscall۰Exit(fn *ssa.Function, args []value) value {
	panic(exitPanic(args[0].(int)))
}
  • externalFn のシグネチャ変更は、外部関数がクロージャではないため slots パラメータが不要であるという最適化です。これにより、インタープリタのコードがよりクリーンになります。
  • ext۰syscall۰Exit の変更は、os.Exit の挙動をテストフレームワーク内で制御するための核心的な変更です。syscall.Exit を直接呼び出す代わりに、カスタムパニック exitPanic を発生させることで、Interpret 関数がこのパニックを捕捉し、終了コードを返すことができるようになります。

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

// 変更前
func Interpret(mainpkg *ssa.Package, mode Mode, filename string, args []string) {

// 変更後
func Interpret(mainpkg *ssa.Package, mode Mode, filename string, args []string) (exitCode int) {

// 変更前 (deferブロックの一部)
	defer func() {
		if complete || i.mode&DisableRecover != 0 {
			return
		}
		// ...
		os.Exit(1)
	}()

// 変更後 (deferブロックの一部)
	exitCode = 2 // デフォルトのパニック終了コード
	defer func() {
		if exitCode != 2 || i.mode&DisableRecover != 0 {
			return
		}
		switch p := recover().(type) {
		case exitPanic: // 新しく追加されたexitPanicのハンドリング
			exitCode = int(p)
			return
		// ...
		default:
			fmt.Fprintln(os.Stderr, "panic: unexpected type: %T", p)
		}
		// os.Exit(1) は削除され、exitCodeが設定される
	}()

// main関数の呼び出し後
	if mainFn := mainpkg.Func("main"); mainFn != nil {
		call(i, nil, token.NoPos, mainFn, nil)
		exitCode = 0 // 正常終了
	} else {
		fmt.Fprintln(os.Stderr, "No main function.")
		exitCode = 1 // main関数がない場合
	}
	// complete = true は削除され、returnでexitCodeが返される
	return
  • Interpret 関数のシグネチャ変更は、インタープリタの実行結果を明示的に返すためのものです。
  • defer ブロック内の exitPanic のハンドリングは、os.Exit の呼び出しをシミュレートし、テストフレームワークが終了コードをプログラム的に取得できるようにするための重要なロジックです。これにより、テストの安定性と信頼性が向上します。
  • mainFn の呼び出し後の exitCode の設定は、プログラムの正常終了、または main 関数が存在しない場合の終了コードを正確に反映させるためのものです。

src/pkg/exp/ssa/interp/interp_test.go (新規ファイル)

package interp_test

import (
	"exp/ssa"
	"exp/ssa/interp"
	"flag"
	"fmt"
	"go/build"
	"strings"
	"testing"
)

// ... (color functions, gorootTests, testdataTests の定義)

func run(t *testing.T, dir, input string) bool {
	// ...
	b := ssa.NewBuilder(ssa.SanityCheckFunctions, ssa.GorootLoader, nil)
	files, err := ssa.ParseFiles(b.Prog.Files, ".", inputs...)
	// ...
	mainpkg, err := b.CreatePackage("main", files)
	// ...
	b.BuildPackage(mainpkg)
	b = nil // discard Builder

	// インタープリタの実行と終了コードのチェック
	if exitCode := interp.Interpret(mainpkg, 0, inputs[0], []string{}); exitCode != 0 {
		t.Errorf("interp.Interpret(%s) exited with code %d, want zero", inputs, exitCode)
		return false
	}
	// ...
	return true
}

func TestInterp(t *testing.T) {
	var failures []string

	for _, input := range testdataTests {
		if !run(t, build.Default.GOROOT+"/src/pkg/exp/ssa/interp/testdata/", input) {
			failures = append(failures, input)
		}
	}

	if !testing.Short() {
		for _, input := range gorootTests {
			if !run(t, build.Default.GOROOT+"/test/", input) {
				failures = append(failures, input)
			}
		}
	}

	// ... (失敗したテストの表示)
}
  • このファイルは、exp/ssa/interp のテストカバレッジを大幅に向上させるためのものです。
  • run 関数は、指定されたGoソースファイルをSSA形式にビルドし、exp/ssa/interp で実行します。interp.Interpret の戻り値 exitCode をチェックすることで、プログラムが期待通りに終了したかを検証します。
  • TestInterp は、$GOROOT/test ディレクトリ内の既存のGoテストファイルや、exp/ssa/interp/testdata/ 内の専用テストファイルをインタープリタで実行します。これにより、インタープリタがGo言語の様々な構文やセマンティクスを正しく解釈できるかを確認します。
  • gorootTests リスト内の多くのテストがコメントアウトされているのは、go/types のシフト演算問題など、当時のインタープリタの制限によるものです。

test/*.go ファイルの変更 (例: test/const.go)

// 変更前
	assert(fhuge == fhuge_1, "fhuge")	// float64 can't distinguish fhuge, fhuge_1.

// 変更後
	// TODO(gri): exp/ssa/interp constant folding is incorrect.
	if os.Getenv("GOSSAINTERP") == "" {
		assert(fhuge == fhuge_1, "fhuge") // float64 can't distinguish fhuge, fhuge_1.
	}
  • これらの変更は、exp/ssa/interp が実行されている場合に、特定のテストロジックをスキップするためのものです。
  • これは、インタープリタがまだ完全にGoの全ての機能をサポートしていない(例: unsafe.Pointer のエミュレーション、recover() の完全なセマンティクス、定数畳み込みの精度など)ための一時的な措置です。
  • os.Getenv("GOSSAINTERP") == "" という条件は、インタープリタが実行されていない(通常のGoコンパイラでビルド・実行されている)場合にのみ、そのテストロジックが実行されることを保証します。

関連リンク

  • Go SSAパッケージのドキュメント (当時のものに近い情報):
    • GoのSSA形式に関する公式ドキュメントやブログ記事は、Goのバージョンアップに伴い更新される可能性があります。当時の情報としては、Goのソースコードリポジトリ内の src/cmd/compile/internal/ssasrc/go/ssa パッケージのドキュメントが参考になります。
  • Go言語の os.Exit ドキュメント:
  • Go言語の panicrecover ドキュメント:
  • Go言語の reflect パッケージドキュメント:
  • Go言語の go/types パッケージドキュメント:

参考にした情報源リンク

コミット

commit aa5aaabb0d18d21d97e9c98d7dfde4899a498335
Author: Alan Donovan <adonovan@google.com>
Date:   Thu Feb 21 12:48:38 2013 -0500

    exp/ssa/interp: (#6 of 5): test interpretation of SSA form of $GOROOT/test/*.go.
    
    The interpreter's os.Exit now triggers a special panic rather
    than kill the test process.  (It's semantically dubious, since
    it will run deferred routines.)  Interpret now returns its
    exit code rather than calling os.Exit.
    
    Also:
    - disabled parts of a few $GOROOT/tests via os.Getenv("GOSSAINTERP").
    - remove unnecessary 'slots' param to external functions; they
      are never closures.
    
    Most of the tests are disabled until go/types supports shifts.
    They can be reenabled if you patch this workaround:
    https://golang.org/cl/7312068
    
    R=iant, bradfitz
    CC=golang-dev, gri
    https://golang.org/cl/7313062

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

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

元コミット内容

このコミットは、Go言語の実験的なSSA(Static Single Assignment)形式インタープリタである exp/ssa/interp のテストフレームワークと動作の改善を目的としています。主な変更点は以下の通りです。

  1. os.Exit の挙動変更: インタープリタ内で os.Exit が呼び出された際に、プロセスを終了させる代わりに特別なパニックを発生させるように変更されました。これにより、テストプロセスが終了することなく、インタープリタの実行を制御できるようになります。ただし、この変更は defer ルーチンが実行されるという点で、本来の os.Exit のセマンティクスとは異なる可能性があります。
  2. Interpret 関数の戻り値: Interpret 関数が終了コードを返すように変更され、os.Exit を直接呼び出す代わりに、呼び出し元が終了コードを処理できるようになりました。
  3. テストの条件付き無効化: $GOROOT/test ディレクトリ内のいくつかのテストが、環境変数 GOSSAINTERP の有無によって条件付きで無効化されるようになりました。これは、インタープリタがまだ完全にGo言語の全機能をサポートしていないため、特に go/types パッケージがシフト演算を正しくサポートするまでの間、一部のテストをスキップするための措置です。
  4. 外部関数の slots パラメータ削除: 外部関数(Goの組み込み関数や標準ライブラリ関数をインタープリタが呼び出すためのラッパー)から不要な slots パラメータが削除されました。これは、これらの関数がクロージャではないため、slots パラメータが不要であると判断されたためです。
  5. 新しいテストファイルの追加: interp_test.gotestdata/coverage.go が追加され、インタープリタのテストカバレッジが向上しました。

変更の背景

このコミットの背景には、Go言語のコンパイラツールチェーンにおけるSSA形式の導入と、そのSSA形式を解釈・実行するインタープリタの開発があります。

Go言語のコンパイラは、コードの最適化をより効率的に行うために、中間表現としてSSA形式を採用しています。exp/ssa パッケージは、GoプログラムのSSA形式を生成・操作するための実験的なツールを提供します。exp/ssa/interp は、このSSA形式のプログラムを実際に実行(解釈)するためのインタープリタであり、コンパイラの開発やデバッグ、あるいはGoプログラムの動作解析などに利用されます。

このコミットが行われた時点では、exp/ssa/interp はまだ開発途上にあり、Go言語の全てのセマンティクスを完全にエミュレートできるわけではありませんでした。特に、以下の課題がありました。

  • os.Exit のテストにおける問題: os.Exit が呼び出されると、テストプロセス全体が終了してしまい、インタープリタのテストが困難になるという問題がありました。テストフレームワーク内で os.Exit の挙動を制御し、テストの継続性を確保する必要がありました。
  • go/types のシフト演算の未サポート: go/types パッケージ(Goの型システムを扱うパッケージ)が、ビットシフト演算(<<, >>)を完全にサポートしていなかったため、シフト演算を含むGoプログラムをSSAインタープリタで正しく解釈できないという問題がありました。これにより、多くの既存のGoテストがインタープリタで実行できない状態でした。
  • 外部関数の効率化: 外部関数呼び出しの際に、不要なパラメータ(slots)が渡されており、コードの冗長性や潜在的な混乱を招いていました。

これらの課題に対処し、exp/ssa/interp の堅牢性とテストカバレッジを向上させることが、このコミットの主要な目的でした。

前提知識の解説

このコミットを理解するためには、以下の概念に関する前提知識が必要です。

  • SSA (Static Single Assignment) 形式:
    • SSA形式は、コンパイラ最適化で広く用いられるプログラムの中間表現です。
    • SSA形式では、各変数が一度だけ代入される(静的に単一代入される)という特性を持ちます。これにより、データフロー解析や最適化が容易になります。
    • 例えば、x = a + b; x = x * 2; というコードは、SSA形式では x1 = a + b; x2 = x1 * 2; のように変換され、各変数の定義と使用が明確になります。
    • Go言語のコンパイラは、内部的にSSA形式を使用してコードを最適化しています。
  • Go言語の exp/ssa パッケージ:
    • exp/ssa は、GoプログラムのSSA形式を生成・操作するための実験的なパッケージです。
    • このパッケージは、GoのソースコードからSSA形式のグラフを構築し、そのグラフを分析・変換するためのAPIを提供します。
    • コンパイラの開発者や、Goプログラムの静的解析ツールを開発する際に利用されます。
  • Go言語の exp/ssa/interp パッケージ:
    • exp/ssa/interp は、exp/ssa パッケージによって生成されたSSA形式のGoプログラムを解釈・実行するインタープリタです。
    • 実際のCPU上で実行するのではなく、SSA形式の命令をソフトウェア的にシミュレートすることでプログラムを実行します。
    • デバッグ、プロファイリング、あるいは特定のコードパスの動作検証などに利用されます。
  • Go言語の os.Exit:
    • os.Exit(code int) は、Goプログラムを終了させるための関数です。
    • この関数が呼び出されると、プログラムは指定された終了コードで直ちに終了します。
    • 重要な点として、os.Exitdefer ステートメントで登録された関数を実行しません。これは、通常のパニックやエラー処理とは異なる挙動です。
  • Go言語の panicrecover:
    • panic は、Goプログラムで回復不能なエラーが発生した際に、プログラムの実行を中断するために使用されます。
    • panic が発生すると、現在のゴルーチンの実行が停止し、defer ステートメントで登録された関数が順次実行されます。
    • recover は、panic から回復するために使用される組み込み関数です。defer 関数内で recover を呼び出すことで、パニックの値を捕捉し、プログラムの実行を継続させることができます。
  • Go言語の reflect パッケージ:
    • reflect パッケージは、実行時にGoプログラムの型情報を検査したり、変数の値を操作したりするための機能を提供します。
    • リフレクションは、汎用的なデータ処理や、Go言語の組み込み機能では実現が難しい動的な操作を行う際に利用されます。
  • Go言語の go/types パッケージ:
    • go/types パッケージは、Go言語の型システムをプログラム的に表現・操作するためのパッケージです。
    • Goコンパイラや静的解析ツールが、Goソースコードの型チェックを行う際に利用します。
    • このコミットの時点では、go/types がビットシフト演算の型推論やチェックに関して一部の制限を抱えていたことが示唆されています。
  • Go CL (Change List):
    • Goプロジェクトでは、コード変更は「Change List」(CL)として提出され、レビューを経てマージされます。
    • golang.org/cl/XXXXXXX の形式で参照されるURLは、特定のCLへのリンクです。

技術的詳細

このコミットの技術的詳細は、exp/ssa/interp の内部実装と、Go言語のランタイム挙動のシミュレーションに関するものです。

  1. os.Exit のパニック化:
    • 従来の os.Exit は、インタープリタの実行を強制的に終了させていました。これはテストの自動化において問題となります。
    • このコミットでは、src/pkg/exp/ssa/interp/external.go 内の ext۰syscall۰Exit 関数が変更され、syscall.Exit を直接呼び出す代わりに panic(exitPanic(args[0].(int))) を実行するようにしました。
    • exitPanic は、interp.go で定義されたカスタム型 type exitPanic int です。
    • Interpret 関数の defer ブロック内で recover() を使用し、この exitPanic 型のパニックを捕捉します。捕捉された場合、パニックの値(終了コード)を Interpret 関数の戻り値として返します。
    • これにより、インタープリタの実行は中断されますが、テストフレームワークはパニックを捕捉し、終了コードを検査できるようになります。ただし、このアプローチは defer ルーチンを実行してしまうため、厳密な os.Exit のセマンティクスとは異なります。
  2. Interpret 関数の戻り値変更:
    • func Interpret(...) のシグネチャが func Interpret(...) (exitCode int) に変更されました。
    • これにより、インタープリタの実行結果(正常終了か、パニックによる終了か、os.Exit による終了か)を数値で表現し、呼び出し元(テストコードなど)がその結果に基づいて適切な処理を行えるようになりました。
    • 正常終了の場合は 0os.Exit の場合はその引数、その他のパニックの場合は 2 が返されます。
  3. 外部関数の slots パラメータ削除:
    • src/pkg/exp/ssa/interp/external.go において、externalFn 型の定義が type externalFn func(fn *ssa.Function, args []value, slots []value) value から type externalFn func(fn *ssa.Function, args []value) value に変更されました。
    • これに伴い、ext۰math۰Float64frombits などの全ての外部関数ラッパーのシグネチャから slots []value パラメータが削除されました。
    • slots パラメータは、GoのSSA形式においてクロージャのキャプチャ変数(フリー変数)を表現するために使用されることがありますが、外部関数はGoのランタイムによって提供される組み込み関数であり、クロージャではないため、このパラメータは不要でした。この変更により、コードの簡潔性と効率性が向上しました。
  4. GOSSAINTERP 環境変数によるテスト制御:
    • test/blank.go, test/cmp.go, test/const.go, test/recover.go などの既存のGoテストファイルに、os.Getenv("GOSSAINTERP") == "" という条件が追加されました。
    • これは、exp/ssa/interp がまだ完全にGoの全ての機能をサポートしていない(特に unsafe.Pointer の扱い、recover() の挙動、定数畳み込みの精度など)ため、インタープリタで実行する際にはこれらのテストの一部をスキップするためのものです。
    • interp.goInterpret 関数内で、os パッケージの初期化時に envs = append(envs, "GOSSAINTERP=1") が追加され、インタープリタ実行時にはこの環境変数が設定されるようになっています。これにより、インタープリタが実行されていることをテストコードが検知し、適切な挙動を選択できます。
  5. go/types のシフト演算問題:
    • コミットメッセージで言及されている go/types のシフト演算問題は、https://golang.org/cl/7312068 で示唆されています。これは、go/types がシフト演算の型チェックや定数評価を正しく行えないバグまたは制限があったことを示しています。
    • この問題のため、interp_test.go で追加された多くの $GOROOT/test ファイルがコメントアウトされています。これは、インタープリタが依存する go/types がシフト演算を正しく扱えないため、これらのテストが失敗する可能性があったためです。

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

このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。

  • src/pkg/exp/ssa/interp/external.go:
    • externalFn 型のシグネチャから slots []value パラメータが削除されました。
    • ext۰syscall۰Exit 関数が syscall.Exit を直接呼び出す代わりに panic(exitPanic(args[0].(int))) を実行するように変更されました。
    • ext۰reflect۰Value۰String が追加されました。
  • src/pkg/exp/ssa/interp/interp.go:
    • Interpret 関数のシグネチャが func Interpret(...) から func Interpret(...) (exitCode int) に変更され、戻り値として終了コードを返すようになりました。
    • Interpret 関数内の defer ブロックが拡張され、exitPanic 型のパニックを捕捉し、その値を exitCode として設定するロジックが追加されました。
    • os パッケージの初期化時に GOSSAINTERP=1 環境変数を設定する処理が追加されました。
    • sizeof_C_MStats の値が 3450 から 3696 に更新されました。
    • callSSA 関数で外部関数を呼び出す際に、slots パラメータが渡されなくなりました。
  • src/pkg/exp/ssa/interp/interp_test.go (新規ファイル):
    • TestInterp 関数が定義され、$GOROOT/test および exp/ssa/interp/testdata/ 内のGoプログラムをSSAインタープリタで実行するテストフレームワークが実装されました。
    • 多くのテストが go/types のシフト演算問題のためにコメントアウトされています。
  • src/pkg/exp/ssa/interp/reflect.go:
    • reflectTypesPackage という新しいパッケージ変数が追加されました。
    • makeNamedType 関数が reflectTypesPackage を使用するように変更されました。
    • ext۰reflect 系の関数のシグネチャから slots []value パラメータが削除されました。
    • initReflect 関数が reflectTypesPackage を使用するように変更されました。
  • test/blank.go, test/cmp.go, test/const.go, test/recover.go:
    • os.Getenv("GOSSAINTERP") == "" という条件分岐が追加され、exp/ssa/interp で実行される際に一部のテストロジック(特に unsafe.Pointer を使用する部分や recover() の挙動に依存する部分、定数畳み込みの精度に依存する部分)がスキップされるようになりました。

コアとなるコードの解説

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

// 変更前
type externalFn func(fn *ssa.Function, args []value, slots []value) value

// 変更後
type externalFn func(fn *ssa.Function, args []value) value

// 変更前 (例)
func ext۰math۰Float64frombits(fn *ssa.Function, args []value, slots []value) value {
	return math.Float64frombits(args[0].(uint64))
}

// 変更後 (例)
func ext۰math۰Float64frombits(fn *ssa.Function, args []value) value {
	return math.Float64frombits(args[0].(uint64))
}

// 変更前
func ext۰syscall۰Exit(fn *ssa.Function, args []value, slots []value) value {
	// We could emulate syscall.Syscall but it's more effort.
	syscall.Exit(args[0].(int))
	return nil
}

// 変更後
func ext۰syscall۰Exit(fn *ssa.Function, args []value) value {
	panic(exitPanic(args[0].(int)))
}
  • externalFn のシグネチャ変更は、外部関数がクロージャではないため slots パラメータが不要であるという最適化です。これにより、インタープリタのコードがよりクリーンになります。
  • ext۰syscall۰Exit の変更は、os.Exit の挙動をテストフレームワーク内で制御するための核心的な変更です。syscall.Exit を直接呼び出す代わりに、カスタムパニック exitPanic を発生させることで、Interpret 関数がこのパニックを捕捉し、終了コードを返すことができるようになります。

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

// 変更前
func Interpret(mainpkg *ssa.Package, mode Mode, filename string, args []string) {

// 変更後
func Interpret(mainpkg *ssa.Package, mode Mode, filename string, args []string) (exitCode int) {

// 変更前 (deferブロックの一部)
	defer func() {
		if complete || i.mode&DisableRecover != 0 {
			return
		}
		// ...
		os.Exit(1)
	}()

// 変更後 (deferブロックの一部)
	exitCode = 2 // デフォルトのパニック終了コード
	defer func() {
		if exitCode != 2 || i.mode&DisableRecover != 0 {
			return
		}
		switch p := recover().(type) {
		case exitPanic: // 新しく追加されたexitPanicのハンドリング
			exitCode = int(p)
			return
		// ...
		default:
			fmt.Fprintln(os.Stderr, "panic: unexpected type: %T", p)
		}
		// os.Exit(1) は削除され、exitCodeが設定される
	}()

// main関数の呼び出し後
	if mainFn := mainpkg.Func("main"); mainFn != nil {
		call(i, nil, token.NoPos, mainFn, nil)
		exitCode = 0 // 正常終了
	} else {
		fmt.Fprintln(os.Stderr, "No main function.")
		exitCode = 1 // main関数がない場合
	}
	// complete = true は削除され、returnでexitCodeが返される
	return
  • Interpret 関数のシグネチャ変更は、インタープリタの実行結果を明示的に返すためのものです。
  • defer ブロック内の exitPanic のハンドリングは、os.Exit の呼び出しをシミュレートし、テストフレームワークが終了コードをプログラム的に取得できるようにするための重要なロジックです。これにより、テストの安定性と信頼性が向上します。
  • mainFn の呼び出し後の exitCode の設定は、プログラムの正常終了、または main 関数が存在しない場合の終了コードを正確に反映させるためのものです。

src/pkg/exp/ssa/interp/interp_test.go (新規ファイル)

package interp_test

import (
	"exp/ssa"
	"exp/ssa/interp"
	"flag"
	"fmt"
	"go/build"
	"strings"
	"testing"
)

// ... (color functions, gorootTests, testdataTests の定義)

func run(t *testing.T, dir, input string) bool {
	// ...
	b := ssa.NewBuilder(ssa.SanityCheckFunctions, ssa.GorootLoader, nil)
	files, err := ssa.ParseFiles(b.Prog.Files, ".", inputs...)
	// ...
	mainpkg, err := b.CreatePackage("main", files)
	// ...
	b.BuildPackage(mainpkg)
	b = nil // discard Builder

	// インタープリタの実行と終了コードのチェック
	if exitCode := interp.Interpret(mainpkg, 0, inputs[0], []string{}); exitCode != 0 {
		t.Errorf("interp.Interpret(%s) exited with code %d, want zero", inputs, exitCode)
		return false
	}
	// ...
	return true
}

func TestInterp(t *testing.T) {
	var failures []string

	for _, input := range testdataTests {
		if !run(t, build.Default.GOROOT+"/src/pkg/exp/ssa/interp/testdata/", input) {
			failures = append(failures, input)
		}
	}

	if !testing.Short() {
		for _, input := range gorootTests {
			if !run(t, build.Default.GOROOT+"/test/", input) {
				failures = append(failures, input)
			}
		}
	}

	// ... (失敗したテストの表示)
}
  • このファイルは、exp/ssa/interp のテストカバレッジを大幅に向上させるためのものです。
  • run 関数は、指定されたGoソースファイルをSSA形式にビルドし、exp/ssa/interp で実行します。interp.Interpret の戻り値 exitCode をチェックすることで、プログラムが期待通りに終了したかを検証します。
  • TestInterp は、$GOROOT/test ディレクトリ内の既存のGoテストファイルや、exp/ssa/interp/testdata/ 内の専用テストファイルをインタープリタで実行します。これにより、インタープリタがGo言語の様々な構文やセマンティクスを正しく解釈できるかを確認します。
  • gorootTests リスト内の多くのテストがコメントアウトされているのは、go/types のシフト演算問題など、当時のインタープリタの制限によるものです。

test/*.go ファイルの変更 (例: test/const.go)

// 変更前
	assert(fhuge == fhuge_1, "fhuge")	// float64 can't distinguish fhuge, fhuge_1.

// 変更後
	// TODO(gri): exp/ssa/interp constant folding is incorrect.
	if os.Getenv("GOSSAINTERP") == "" {
		assert(fhuge == fhuge_1, "fhuge") // float64 can't distinguish fhuge, fhuge_1.
	}
  • これらの変更は、exp/ssa/interp が実行されている場合に、特定のテストロジックをスキップするためのものです。
  • これは、インタープリタがまだ完全にGoの全ての機能をサポートしていない(例: unsafe.Pointer のエミュレーション、recover() の完全なセマンティクス、定数畳み込みの精度など)ための一時的な措置です。
  • os.Getenv("GOSSAINTERP") == "" という条件は、インタープリタが実行されていない(通常のGoコンパイラでビルド・実行されている)場合にのみ、そのテストロジックが実行されることを保証します。

関連リンク

  • Go SSAパッケージのドキュメント (当時のものに近い情報):
    • GoのSSA形式に関する公式ドキュメントやブログ記事は、Goのバージョンアップに伴い更新される可能性があります。当時の情報としては、Goのソースコードリポジトリ内の src/cmd/compile/internal/ssasrc/go/ssa パッケージのドキュメントが参考になります。
  • Go言語の os.Exit ドキュメント:
  • Go言語の panicrecover ドキュメント:
  • Go言語の reflect パッケージドキュメント:
  • Go言語の go/types パッケージドキュメント:

参考にした情報源リンク