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

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

このコミットは、Go言語の reflect パッケージにおけるランタイムコード生成の使用を停止することを目的としています。具体的には、MakeFunc 関数が動的にアセンブリコードを生成する代わりに、静的に定義された makeFuncStub アセンブリ関数と、makeFuncImpl 構造体内のコンテキストポインタを利用する方式へと変更されています。これにより、Go 1.1 の関数呼び出し規約の変更(http://golang.org/s/go11func で詳述されているステップ3)に対応し、関連するバグ(Issue #3736, #3738, #4081)を修正します。

コミット

commit b1b67a36ace635744cd261ee6f3441d1044c66b3
Author: Russ Cox <rsc@golang.org>
Date:   Fri Feb 22 15:23:57 2013 -0500

    reflect: stop using run-time code generation
    
    Step 3 of http://golang.org/s/go11func.
    
    Fixes #3736.
    Fixes #3738.
    Fixes #4081.
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/7393050

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

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

元コミット内容

reflect: stop using run-time code generation

Step 3 of http://golang.org/s/go11func.

Fixes #3736.
Fixes #3738.
Fixes #4081.

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/7393050

変更の背景

この変更は、Go 1.1 のリリースに向けた重要なステップの一部です。Go 1.1 では、Go関数の呼び出し規約が変更され、特に reflect.MakeFunc のような動的に関数を生成する機能に影響を与えました。以前のGoバージョンでは、MakeFunc は実行時に特定のアーキテクチャ向けのアセンブリコードを動的に生成し、そのコードを呼び出すことでリフレクションによる関数呼び出しを実現していました。

しかし、この動的なコード生成はいくつかの問題を引き起こしていました。

  1. 複雑性: 各アーキテクチャ(amd64, 386, armなど)ごとに異なるアセンブリコードテンプレートを管理する必要があり、コードベースが複雑化していました。
  2. セキュリティ: 実行時にコードを生成し実行することは、セキュリティ上の懸念(例えば、コードインジェクション攻撃のリスク)を増大させる可能性があります。
  3. パフォーマンス: 動的なコード生成とキャッシュフラッシュのオーバーヘッドがありました。
  4. Go 1.1 呼び出し規約の変更への対応: Go 1.1 で導入された新しい関数呼び出し規約(特に、関数ポインタが実際のコードエントリポイントへのポインタと、その関数が使用するコンテキストデータへのポインタのペアになるという変更)に、既存の動的コード生成アプローチでは効率的に対応できませんでした。

このコミットは、http://golang.org/s/go11func で説明されている「Go 1.1 の関数呼び出し規約の変更」のステップ3に該当します。このステップの目標は、MakeFunc が動的なコード生成に依存するのをやめ、よりシンプルで堅牢なメカニズムに移行することでした。

具体的に修正されるIssueは以下の通りです。

  • Issue #3736: reflect: MakeFunc is slow: MakeFunc のパフォーマンス問題。動的なコード生成がボトルネックの一つでした。
  • Issue #3738: reflect: MakeFunc needs to be able to return a closure: MakeFunc がクロージャを正しく扱えるようにする必要がありました。これは新しい呼び出し規約と関連しています。
  • Issue #4081: reflect: MakeFunc needs to be able to return a function with a receiver: レシーバを持つメソッドを MakeFunc で生成する際の課題。これも新しい呼び出し規約と密接に関連しています。

これらの問題を解決し、Go 1.1 の新しい関数呼び出し規約に準拠するために、ランタイムコード生成の廃止が決定されました。

前提知識の解説

Go言語の reflect パッケージ

reflect パッケージは、Goプログラムが実行時に自身の構造を検査(リフレクション)し、操作するための機能を提供します。これにより、型情報、フィールド、メソッドなどを動的に取得・操作したり、実行時に新しい関数を生成したりすることが可能になります。

  • reflect.Type: Goの型を表します。
  • reflect.Value: Goの値を表します。
  • reflect.MakeFunc: 指定された関数型 Type と、その関数が呼び出されたときに実行されるGo関数 fn を受け取り、新しい reflect.Value 型の関数を生成します。この生成された関数は、Goの通常の関数として呼び出すことができます。

ランタイムコード生成 (Runtime Code Generation)

ランタイムコード生成とは、プログラムの実行中に機械語コードを動的に生成し、それをメモリに配置して実行する技術です。JIT (Just-In-Time) コンパイラなどで利用されます。Goの reflect.MakeFunc は、以前はこの技術を使用して、与えられたGo関数 fn を呼び出すための小さなアセンブリスタブを動的に生成していました。

Go 1.1 の関数呼び出し規約の変更 (http://golang.org/s/go11func)

Go 1.1 では、Go関数の内部表現と呼び出し規約が変更されました。主な変更点は以下の通りです。

  • 関数ポインタの構造: 以前は、Goの関数ポインタは直接その関数のコードエントリポイントを指していました。Go 1.1 以降、関数ポインタは2つのポインタのペアになりました。
    1. コードポインタ: 実際の関数の機械語コードの開始アドレスを指します。
    2. コンテキストポインタ (Closure Pointer): その関数がクロージャである場合、そのクロージャがキャプチャした外部変数を保持するデータ構造(環境)へのポインタを指します。通常の(非クロージャ)関数では、このコンテキストポインタは nil になります。

この変更により、Goの関数はより柔軟にクロージャを表現できるようになり、ガベージコレクションの効率も向上しました。しかし、MakeFunc のように動的に関数を生成する際には、この新しい関数ポインタの構造を正しく扱う必要が生じました。

アセンブリ言語 (x86, x86-64, ARM)

Goのランタイムは、パフォーマンスが重要な部分や、Go言語自体では直接アクセスできない低レベルな操作(例えば、スタックフレームの操作やレジスタの直接操作)を行うためにアセンブリ言語を使用します。

  • x86 (386): 32ビットIntel互換プロセッサのアセンブリ言語。
  • x86-64 (amd64): 64ビットIntel互換プロセッサのアセンブリ言語。
  • ARM: ARMアーキテクチャプロセッサのアセンブリ言語。モバイルデバイスなどで広く使われています。

これらのアセンブリコードは、Goの関数呼び出し規約に従って引数をレジスタやスタックに配置し、関数を呼び出す役割を担います。

技術的詳細

このコミットの核心は、reflect.MakeFunc が動的にアセンブリコードを生成するのをやめ、代わりに静的に定義されたアセンブリスタブ makeFuncStub を利用するように変更した点です。

変更前のアプローチ

変更前は、MakeFunc が呼び出されるたびに、ターゲットアーキテクチャ(amd64, 386, arm)に応じたアセンブリコードのテンプレート(amd64CallStub, _386CallStub, armCallStub)をコピーし、その中に MakeFunc に渡された typ (型情報) と fn (Go関数) のポインタを埋め込んでいました。この動的に生成されたコードが、makeFuncStub へジャンプする役割を担っていました。

このアプローチでは、以下の問題がありました。

  • 動的なメモリ割り当てと実行権限: 生成されたコードを格納するためにメモリを割り当て、実行可能にする必要がありました。
  • キャッシュフラッシュ: コードキャッシュの一貫性を保つために cacheflush のような操作が必要でした。
  • 複雑なアセンブリテンプレート: 各アーキテクチャごとに異なるアセンブリコードを管理する必要がありました。

変更後のアプローチ

変更後、MakeFunc は動的なコード生成を行いません。代わりに、Go 1.1 の新しい関数呼び出し規約を利用します。

  1. makeFuncImpl 構造体の変更:

    • 以前は codeptr unsafe.Pointercode [40]byte を持っており、動的に生成されたアセンブリコードを保持していました。
    • 変更後、これらは削除され、代わりに code uintptr が追加されました。この code フィールドは、静的に定義されたアセンブリ関数 makeFuncStub のエントリポイントを指します。
    • typ *rtype*funcType に変更され、より具体的な関数型を指すようになりました。
    • fn func([]Value) []Value は、リフレクション呼び出しの際に実際に実行されるGo関数です。
  2. MakeFunc の実装変更:

    • MakeFunc は、makeFuncStub のコードアドレスを dummy := makeFuncStub のようにして取得し、それを makeFuncImplcode フィールドに設定します。
    • makeFuncImpl のインスタンスが、生成される reflect.Value の内部データとして使用されます。この makeFuncImpl インスタンス自体が、Go 1.1 の新しい関数呼び出し規約における「コンテキストポインタ」の役割を果たします。
  3. makeFuncStub アセンブリ関数の変更:

    • makeFuncStub は、以前は動的に生成されたコードからジャンプされてくることを想定し、AX, BX, CX (amd64/386) や R0, R1, R2 (arm) レジスタに type, fn, frame を受け取っていました。
    • 変更後、makeFuncStub はGo 1.1 の新しい関数呼び出し規約に従い、コンテキストレジスタ(アーキテクチャによって異なる)に *makeFuncImpl のポインタを受け取るように変更されました。
    • makeFuncStub の役割は、受け取った makeFuncImpl のポインタから typfn を抽出し、引数フレームのポインタと共に callReflect 関数を呼び出すことです。
  4. callReflect 関数の変更:

    • 以前は func callReflect(ftyp *funcType, f func([]Value) []Value, frame unsafe.Pointer) というシグネチャでした。
    • 変更後、func callReflect(ctxt *makeFuncImpl, frame unsafe.Pointer) となり、makeFuncImpl のポインタを直接受け取るようになりました。
    • callReflect の内部で、ctxt.typctxt.fn を利用して、実際のGo関数呼び出しを行います。

利点

  • シンプル化: 動的なコード生成ロジックと、それに伴うアーキテクチャ固有のアセンブリテンプレートが不要になりました。
  • 堅牢性: 静的に定義されたアセンブリスタブを使用することで、セキュリティリスクが低減し、デバッグも容易になります。
  • Go 1.1 呼び出し規約への準拠: 新しい関数ポインタの構造(コードポインタとコンテキストポインタのペア)を自然に利用できるようになりました。makeFuncImpl インスタンスがコンテキストポインタとして機能します。
  • パフォーマンス向上: 動的なコード生成とキャッシュフラッシュのオーバーヘッドがなくなりました。

この変更により、reflect.MakeFunc はGoの新しい関数呼び出し規約に完全に適合し、より効率的で安全なリフレクション機能を提供できるようになりました。

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

src/pkg/reflect/asm_386.s, src/pkg/reflect/asm_amd64.s, src/pkg/reflect/asm_arm.s

  • makeFuncStub のアセンブリコードが大幅に簡素化されました。
  • 以前は AX, BX, CX (386/amd64) や R0, R1, R2 (ARM) レジスタに type, fn, frame を受け取っていましたが、変更後はコンテキストレジスタ(DX for 386/amd64, R7 for ARM)に makeFuncImpl のポインタを受け取り、スタックフレームのポインタを CX (386/amd64) や R1 (ARM) に設定するように変更されました。
  • 不要になった cacheflush の定義が削除されました。

src/pkg/reflect/makefunc.go

  • makeFuncImpl 構造体から codeptr unsafe.Pointercode [40]byte が削除され、code uintptr が追加されました。
  • typ *rtypetyp *funcType に変更されました。
  • MakeFunc 関数内の動的なアセンブリコード生成ロジック(switch runtime.GOARCH ブロック)が完全に削除されました。
  • MakeFunc は、makeFuncStub のコードアドレスを直接取得し、それを makeFuncImplcode フィールドに設定するようになりました。
  • cacheflush 関数の宣言が削除されました。
  • amd64CallStub, _386CallStub, armCallStub といったアセンブリコードテンプレートの定義が削除されました。

src/pkg/reflect/value.go

  • callReflect 関数のシグネチャが変更されました。以前は ftyp *funcType, f func([]Value) []Value, frame unsafe.Pointer を引数に取っていましたが、変更後は ctxt *makeFuncImpl, frame unsafe.Pointer を引数に取るようになりました。
  • callReflect の内部で、ctxt.typctxt.fn を利用して、実際の型情報とGo関数を取得するようになりました。

コアとなるコードの解説

src/pkg/reflect/makefunc.go の変更

type makeFuncImpl struct {
	code uintptr // makeFuncStub のエントリポイント
	typ  *funcType // 関数の型情報
	fn   func([]Value) []Value // 実際に呼び出されるGo関数
}

func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value {
	// ... (型チェックなど)

	ftyp := (*funcType)(unsafe.Pointer(t)) // 型情報を funcType にキャスト

	// makeFuncStub のコードアドレスを取得
	// Go 1.1 の関数ポインタの構造を利用:
	// dummy は makeFuncStub の関数ポインタ。
	// そのポインタのポインタを unsafe.Pointer で uintptrs にキャストし、
	// 最初の要素(コードアドレス)を取得する。
	dummy := makeFuncStub
	code := **(**uintptr)(unsafe.Pointer(&dummy))

	// makeFuncImpl インスタンスを作成
	// code: makeFuncStub のアドレス
	// typ: 関数の型情報
	// fn: 実際に実行されるGo関数
	impl := &makeFuncImpl{code: code, typ: ftyp, fn: fn}

	// reflect.Value を作成して返す
	// impl が Go 1.1 の新しい関数呼び出し規約におけるコンテキストポインタとして機能する
	return Value{t, unsafe.Pointer(impl), flag(Func) << flagKindShift}
}

この変更により、MakeFunc は動的にアセンブリコードを生成する代わりに、静的にコンパイルされた makeFuncStub のアドレスを makeFuncImpl 構造体に格納するようになりました。makeFuncImpl のインスタンス自体が、Go 1.1 の新しい関数呼び出し規約における「コンテキストポインタ」として機能します。つまり、MakeFunc によって生成された reflect.Value が呼び出されると、その内部の makeFuncImpl インスタンスがコンテキストとして makeFuncStub に渡されます。

src/pkg/reflect/asm_*.s の変更 (例: asm_amd64.s)

// makeFuncStub is the code half of the function returned by MakeFunc.
// See the comment on the declaration of makeFuncStub in value.go
// for more details.
TEXT ·makeFuncStub(SB),7,$16
	MOVQ	DX, 0(SP) // DX (コンテキストレジスタ) の値をスタックに保存 (makeFuncImpl ポインタ)
	LEAQ	arg+0(FP), CX // 引数フレームの先頭アドレスを CX にロード
	MOVQ	CX, 8(SP) // CX の値をスタックに保存 (フレームポインタ)
	CALL	·callReflect(SB) // callReflect を呼び出す
	RET // 呼び出し元に戻る

makeFuncStub は、Go 1.1 の新しい関数呼び出し規約に従って、コンテキストレジスタ(amd64では DX)に makeFuncImpl のポインタを受け取ります。このポインタと、引数フレームのポインタをスタックにプッシュし、callReflect 関数を呼び出します。これにより、callReflectmakeFuncImpl から必要な情報(元のGo関数 fn と型 typ)を取得し、リフレクションによる関数呼び出しを実行できます。

src/pkg/reflect/value.go の変更

func callReflect(ctxt *makeFuncImpl, frame unsafe.Pointer) {
	ftyp := ctxt.typ // makeFuncImpl から型情報を取得
	f := ctxt.fn     // makeFuncImpl から元のGo関数を取得

	// ... (引数フレームの処理、f の呼び出しなど)
}

callReflect は、makeFuncStub から渡された makeFuncImpl のポインタ ctxt を受け取ります。この ctxt から、MakeFunc に渡された元のGo関数 f とその型 ftyp を取得し、リフレクションによる実際の関数呼び出し処理を行います。

これらの変更により、Goの reflect パッケージは、動的なコード生成という複雑でアーキテクチャ依存のメカニズムから解放され、Go 1.1 で導入された新しい関数呼び出し規約に準拠した、よりクリーンで効率的な実装へと進化しました。

関連リンク

参考にした情報源リンク

このコミットは、Go言語の reflect パッケージにおけるランタイムコード生成の使用を停止することを目的としています。具体的には、MakeFunc 関数が動的にアセンブリコードを生成する代わりに、静的に定義された makeFuncStub アセンブリ関数と、makeFuncImpl 構造体内のコンテキストポインタを利用する方式へと変更されています。これにより、Go 1.1 の関数呼び出し規約の変更(http://golang.org/s/go11func で詳述されているステップ3)に対応し、関連するバグ(Issue #3736, #3738, #4081)を修正します。

コミット

commit b1b67a36ace635744cd261ee6f3441d1044c66b3
Author: Russ Cox <rsc@golang.org>
Date:   Fri Feb 22 15:23:57 2013 -0500

    reflect: stop using run-time code generation
    
    Step 3 of http://golang.org/s/go11func.
    
    Fixes #3736.
    Fixes #3738.
    Fixes #4081.
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/7393050

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

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

元コミット内容

reflect: stop using run-time code generation

Step 3 of http://golang.org/s/go11func.

Fixes #3736.
Fixes #3738.
Fixes #4081.

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/7393050

変更の背景

この変更は、Go 1.1 のリリースに向けた重要なステップの一部です。Go 1.1 では、Go関数の呼び出し規約が変更され、特に reflect.MakeFunc のような動的に関数を生成する機能に影響を与えました。以前のGoバージョンでは、MakeFunc は実行時に特定のアーキテクチャ向けのアセンブリコードを動的に生成し、そのコードを呼び出すことでリフレクションによる関数呼び出しを実現していました。

しかし、この動的なコード生成はいくつかの問題を引き起こしていました。

  1. 複雑性: 各アーキテクチャ(amd64, 386, armなど)ごとに異なるアセンブリコードテンプレートを管理する必要があり、コードベースが複雑化していました。
  2. セキュリティ: 実行時にコードを生成し実行することは、セキュリティ上の懸念(例えば、コードインジェクション攻撃のリスク)を増大させる可能性があります。
  3. パフォーマンス: 動的なコード生成とキャッシュフラッシュのオーバーヘッドがありました。
  4. Go 1.1 呼び出し規約の変更への対応: Go 1.1 で導入された新しい関数呼び出し規約(特に、関数ポインタが実際のコードエントリポイントへのポインタと、その関数が使用するコンテキストデータへのポインタのペアになるという変更)に、既存の動的コード生成アプローチでは効率的に対応できませんでした。

このコミットは、http://golang.org/s/go11func で説明されている「Go 1.1 の関数呼び出し規約の変更」のステップ3に該当します。このステップの目標は、MakeFunc が動的なコード生成に依存するのをやめ、よりシンプルで堅牢なメカニズムに移行することでした。

具体的に修正されるIssueは以下の通りです。

  • Issue #3736: reflect: MakeFunc is slow: MakeFunc のパフォーマンス問題。動的なコード生成がボトルネックの一つでした。
  • Issue #3738: reflect: MakeFunc needs to be able to return a closure: MakeFunc がクロージャを正しく扱えるようにする必要がありました。これは新しい呼び出し規約と関連しています。
  • Issue #4081: reflect: MakeFunc needs to be able to return a function with a receiver: レシーバを持つメソッドを MakeFunc で生成する際の課題。これも新しい呼び出し規約と密接に関連しています。

これらの問題を解決し、Go 1.1 の新しい関数呼び出し規約に準拠するために、ランタイムコード生成の廃止が決定されました。

前提知識の解説

Go言語の reflect パッケージ

reflect パッケージは、Goプログラムが実行時に自身の構造を検査(リフレクション)し、操作するための機能を提供します。これにより、型情報、フィールド、メソッドなどを動的に取得・操作したり、実行時に新しい関数を生成したりすることが可能になります。

  • reflect.Type: Goの型を表します。
  • reflect.Value: Goの値を表します。
  • reflect.MakeFunc: 指定された関数型 Type と、その関数が呼び出されたときに実行されるGo関数 fn を受け取り、新しい reflect.Value 型の関数を生成します。この生成された関数は、Goの通常の関数として呼び出すことができます。

ランタイムコード生成 (Runtime Code Generation)

ランタイムコード生成とは、プログラムの実行中に機械語コードを動的に生成し、それをメモリに配置して実行する技術です。JIT (Just-In-Time) コンパイラなどで利用されます。Goの reflect.MakeFunc は、以前はこの技術を使用して、与えられたGo関数 fn を呼び出すための小さなアセンブリスタブを動的に生成していました。

Go 1.1 の関数呼び出し規約の変更 (http://golang.org/s/go11func)

Go 1.1 では、Go関数の内部表現と呼び出し規約が変更されました。主な変更点は以下の通りです。

  • 関数ポインタの構造: 以前は、Goの関数ポインタは直接その関数のコードエントリポイントを指していました。Go 1.1 以降、関数ポインタは2つのポインタのペアになりました。
    1. コードポインタ: 実際の関数の機械語コードの開始アドレスを指します。
    2. コンテキストポインタ (Closure Pointer): その関数がクロージャである場合、そのクロージャがキャプチャした外部変数を保持するデータ構造(環境)へのポインタを指します。通常の(非クロージャ)関数では、このコンテキストポインタは nil になります。

この変更により、Goの関数はより柔軟にクロージャを表現できるようになり、ガベージコレクションの効率も向上しました。しかし、MakeFunc のように動的に関数を生成する際には、この新しい関数ポインタの構造を正しく扱う必要が生じました。

アセンブリ言語 (x86, x86-64, ARM)

Goのランタイムは、パフォーマンスが重要な部分や、Go言語自体では直接アクセスできない低レベルな操作(例えば、スタックフレームの操作やレジスタの直接操作)を行うためにアセンブリ言語を使用します。

  • x86 (386): 32ビットIntel互換プロセッサのアセンブリ言語。
  • x86-64 (amd64): 64ビットIntel互換プロセッサのアセンブリ言語。
  • ARM: ARMアーキテクチャプロセッサのアセンブリ言語。モバイルデバイスなどで広く使われています。

これらのアセンブリコードは、Goの関数呼び出し規約に従って引数をレジスタやスタックに配置し、関数を呼び出す役割を担います。

技術的詳細

このコミットの核心は、reflect.MakeFunc が動的にアセンブリコードを生成するのをやめ、代わりに静的に定義されたアセンブリスタブ makeFuncStub を利用するように変更した点です。

変更前のアプローチ

変更前は、MakeFunc が呼び出されるたびに、ターゲットアーキテクチャ(amd64, 386, arm)に応じたアセンブリコードのテンプレート(amd64CallStub, _386CallStub, armCallStub)をコピーし、その中に MakeFunc に渡された typ (型情報) と fn (Go関数) のポインタを埋め込んでいました。この動的に生成されたコードが、makeFuncStub へジャンプする役割を担っていました。

このアプローチでは、以下の問題がありました。

  • 動的なメモリ割り当てと実行権限: 生成されたコードを格納するためにメモリを割り当て、実行可能にする必要がありました。
  • キャッシュフラッシュ: コードキャッシュの一貫性を保つために cacheflush のような操作が必要でした。
  • 複雑なアセンブリテンプレート: 各アーキテクチャごとに異なるアセンブリコードを管理する必要がありました。

変更後のアプローチ

変更後、MakeFunc は動的なコード生成を行いません。代わりに、Go 1.1 の新しい関数呼び出し規約を利用します。

  1. makeFuncImpl 構造体の変更:

    • 以前は codeptr unsafe.Pointercode [40]byte を持っており、動的に生成されたアセンブリコードを保持していました。
    • 変更後、これらは削除され、代わりに code uintptr が追加されました。この code フィールドは、静的に定義されたアセンブリ関数 makeFuncStub のエントリポイントを指します。
    • typ *rtype*funcType に変更され、より具体的な関数型を指すようになりました。
    • fn func([]Value) []Value は、リフレクション呼び出しの際に実際に実行されるGo関数です。
  2. MakeFunc の実装変更:

    • MakeFunc は、makeFuncStub のコードアドレスを dummy := makeFuncStub のようにして取得し、それを makeFuncImplcode フィールドに設定します。
    • makeFuncImpl のインスタンスが、生成される reflect.Value の内部データとして使用されます。この makeFuncImpl インスタンス自体が、Go 1.1 の新しい関数呼び出し規約における「コンテキストポインタ」の役割を果たします。
  3. makeFuncStub アセンブリ関数の変更:

    • makeFuncStub は、以前は動的に生成されたコードからジャンプされてくることを想定し、AX, BX, CX (amd64/386) や R0, R1, R2 (arm) レジスタに type, fn, frame を受け取っていました。
    • 変更後、makeFuncStub はGo 1.1 の新しい関数呼び出し規約に従い、コンテキストレジスタ(アーキテクチャによって異なる)に *makeFuncImpl のポインタを受け取るように変更されました。
    • makeFuncStub の役割は、受け取った makeFuncImpl のポインタから typfn を抽出し、引数フレームのポインタと共に callReflect 関数を呼び出すことです。
  4. callReflect 関数の変更:

    • 以前は func callReflect(ftyp *funcType, f func([]Value) []Value, frame unsafe.Pointer) というシグネチャでした。
    • 変更後、func callReflect(ctxt *makeFuncImpl, frame unsafe.Pointer) となり、makeFuncImpl のポインタを直接受け取るようになりました。
    • callReflect の内部で、ctxt.typctxt.fn を利用して、実際のGo関数呼び出しを行います。

利点

  • シンプル化: 動的なコード生成ロジックと、それに伴うアーキテクチャ固有のアセンブリテンプレートが不要になりました。
  • 堅牢性: 静的に定義されたアセンブリスタブを使用することで、セキュリティリスクが低減し、デバッグも容易になります。
  • Go 1.1 呼び出し規約への準拠: 新しい関数ポインタの構造(コードポインタとコンテキストポインタのペア)を自然に利用できるようになりました。makeFuncImpl インスタンスがコンテキストポインタとして機能します。
  • パフォーマンス向上: 動的なコード生成とキャッシュフラッシュのオーバーヘッドがなくなりました。

この変更により、reflect.MakeFunc はGoの新しい関数呼び出し規約に完全に適合し、より効率的で安全なリフレクション機能を提供できるようになりました。

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

src/pkg/reflect/asm_386.s, src/pkg/reflect/asm_amd64.s, src/pkg/reflect/asm_arm.s

  • makeFuncStub のアセンブリコードが大幅に簡素化されました。
  • 以前は AX, BX, CX (386/amd64) や R0, R1, R2 (ARM) レジスタに type, fn, frame を受け取っていましたが、変更後はコンテキストレジスタ(DX for 386/amd64, R7 for ARM)に makeFuncImpl のポインタを受け取り、スタックフレームのポインタを CX (386/amd64) や R1 (ARM) に設定するように変更されました。
  • 不要になった cacheflush の定義が削除されました。

src/pkg/reflect/makefunc.go

  • makeFuncImpl 構造体から codeptr unsafe.Pointercode [40]byte が削除され、code uintptr が追加されました。
  • typ *rtypetyp *funcType に変更されました。
  • MakeFunc 関数内の動的なアセンブリコード生成ロジック(switch runtime.GOARCH ブロック)が完全に削除されました。
  • MakeFunc は、makeFuncStub のコードアドレスを直接取得し、それを makeFuncImplcode フィールドに設定するようになりました。
  • cacheflush 関数の宣言が削除されました。
  • amd64CallStub, _386CallStub, armCallStub といったアセンブリコードテンプレートの定義が削除されました。

src/pkg/reflect/value.go

  • callReflect 関数のシグネチャが変更されました。以前は ftyp *funcType, f func([]Value) []Value, frame unsafe.Pointer を引数に取っていましたが、変更後は ctxt *makeFuncImpl, frame unsafe.Pointer を引数に取るようになりました。
  • callReflect の内部で、ctxt.typctxt.fn を利用して、実際の型情報とGo関数を取得するようになりました。

コアとなるコードの解説

src/pkg/reflect/makefunc.go の変更

type makeFuncImpl struct {
	code uintptr // makeFuncStub のエントリポイント
	typ  *funcType // 関数の型情報
	fn   func([]Value) []Value // 実際に呼び出されるGo関数
}

func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value {
	// ... (型チェックなど)

	ftyp := (*funcType)(unsafe.Pointer(t)) // 型情報を funcType にキャスト

	// makeFuncStub のコードアドレスを取得
	// Go 1.1 の関数ポインタの構造を利用:
	// dummy は makeFuncStub の関数ポインタ。
	// そのポインタのポインタを unsafe.Pointer で uintptrs にキャストし、
	// 最初の要素(コードアドレス)を取得する。
	dummy := makeFuncStub
	code := **(**uintptr)(unsafe.Pointer(&dummy))

	// makeFuncImpl インスタンスを作成
	// code: makeFuncStub のアドレス
	// typ: 関数の型情報
	// fn: 実際に実行されるGo関数
	impl := &makeFuncImpl{code: code, typ: ftyp, fn: fn}

	// reflect.Value を作成して返す
	// impl が Go 1.1 の新しい関数呼び出し規約におけるコンテキストポインタとして機能する
	return Value{t, unsafe.Pointer(impl), flag(Func) << flagKindShift}
}

この変更により、MakeFunc は動的にアセンブリコードを生成する代わりに、静的にコンパイルされた makeFuncStub のアドレスを makeFuncImpl 構造体に格納するようになりました。makeFuncImpl のインスタンス自体が、Go 1.1 の新しい関数呼び出し規約における「コンテキストポインタ」として機能します。つまり、MakeFunc によって生成された reflect.Value が呼び出されると、その内部の makeFuncImpl インスタンスがコンテキストとして makeFuncStub に渡されます。

src/pkg/reflect/asm_*.s の変更 (例: asm_amd64.s)

// makeFuncStub is the code half of the function returned by MakeFunc.
// See the comment on the declaration of makeFuncStub in value.go
// for more details.
TEXT ·makeFuncStub(SB),7,$16
	MOVQ	DX, 0(SP) // DX (コンテキストレジスタ) の値をスタックに保存 (makeFuncImpl ポインタ)
	LEAQ	arg+0(FP), CX // 引数フレームの先頭アドレスを CX にロード
	MOVQ	CX, 8(SP) // CX の値をスタックに保存 (フレームポインタ)
	CALL	·callReflect(SB) // callReflect を呼び出す
	RET // 呼び出し元に戻る

makeFuncStub は、Go 1.1 の新しい関数呼び出し規約に従って、コンテキストレジスタ(amd64では DX)に makeFuncImpl のポインタを受け取ります。このポインタと、引数フレームのポインタをスタックにプッシュし、callReflect 関数を呼び出します。これにより、callReflectmakeFuncImpl から必要な情報(元のGo関数 fn と型 typ)を取得し、リフレクションによる関数呼び出しを実行できます。

src/pkg/reflect/value.go の変更

func callReflect(ctxt *makeFuncImpl, frame unsafe.Pointer) {
	ftyp := ctxt.typ // makeFuncImpl から型情報を取得
	f := ctxt.fn     // makeFuncImpl から元のGo関数を取得

	// ... (引数フレームの処理、f の呼び出しなど)
}

callReflect は、makeFuncStub から渡された makeFuncImpl のポインタ ctxt を受け取ります。この ctxt から、MakeFunc に渡された元のGo関数 f とその型 ftyp を取得し、リフレクションによる実際の関数呼び出し処理を行います。

これらの変更により、Goの reflect パッケージは、動的なコード生成という複雑でアーキテクチャ依存のメカニズムから解放され、Go 1.1 で導入された新しい関数呼び出し規約に準拠した、よりクリーンで効率的な実装へと進化しました。

関連リンク

参考にした情報源リンク