[インデックス 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
は実行時に特定のアーキテクチャ向けのアセンブリコードを動的に生成し、そのコードを呼び出すことでリフレクションによる関数呼び出しを実現していました。
しかし、この動的なコード生成はいくつかの問題を引き起こしていました。
- 複雑性: 各アーキテクチャ(amd64, 386, armなど)ごとに異なるアセンブリコードテンプレートを管理する必要があり、コードベースが複雑化していました。
- セキュリティ: 実行時にコードを生成し実行することは、セキュリティ上の懸念(例えば、コードインジェクション攻撃のリスク)を増大させる可能性があります。
- パフォーマンス: 動的なコード生成とキャッシュフラッシュのオーバーヘッドがありました。
- 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つのポインタのペアになりました。
- コードポインタ: 実際の関数の機械語コードの開始アドレスを指します。
- コンテキストポインタ (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 の新しい関数呼び出し規約を利用します。
-
makeFuncImpl
構造体の変更:- 以前は
codeptr unsafe.Pointer
とcode [40]byte
を持っており、動的に生成されたアセンブリコードを保持していました。 - 変更後、これらは削除され、代わりに
code uintptr
が追加されました。このcode
フィールドは、静的に定義されたアセンブリ関数makeFuncStub
のエントリポイントを指します。 typ *rtype
は*funcType
に変更され、より具体的な関数型を指すようになりました。fn func([]Value) []Value
は、リフレクション呼び出しの際に実際に実行されるGo関数です。
- 以前は
-
MakeFunc
の実装変更:MakeFunc
は、makeFuncStub
のコードアドレスをdummy := makeFuncStub
のようにして取得し、それをmakeFuncImpl
のcode
フィールドに設定します。makeFuncImpl
のインスタンスが、生成されるreflect.Value
の内部データとして使用されます。このmakeFuncImpl
インスタンス自体が、Go 1.1 の新しい関数呼び出し規約における「コンテキストポインタ」の役割を果たします。
-
makeFuncStub
アセンブリ関数の変更:makeFuncStub
は、以前は動的に生成されたコードからジャンプされてくることを想定し、AX
,BX
,CX
(amd64/386) やR0
,R1
,R2
(arm) レジスタにtype
,fn
,frame
を受け取っていました。- 変更後、
makeFuncStub
はGo 1.1 の新しい関数呼び出し規約に従い、コンテキストレジスタ(アーキテクチャによって異なる)に*makeFuncImpl
のポインタを受け取るように変更されました。 makeFuncStub
の役割は、受け取ったmakeFuncImpl
のポインタからtyp
とfn
を抽出し、引数フレームのポインタと共にcallReflect
関数を呼び出すことです。
-
callReflect
関数の変更:- 以前は
func callReflect(ftyp *funcType, f func([]Value) []Value, frame unsafe.Pointer)
というシグネチャでした。 - 変更後、
func callReflect(ctxt *makeFuncImpl, frame unsafe.Pointer)
となり、makeFuncImpl
のポインタを直接受け取るようになりました。 callReflect
の内部で、ctxt.typ
とctxt.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.Pointer
とcode [40]byte
が削除され、code uintptr
が追加されました。typ *rtype
がtyp *funcType
に変更されました。MakeFunc
関数内の動的なアセンブリコード生成ロジック(switch runtime.GOARCH
ブロック)が完全に削除されました。MakeFunc
は、makeFuncStub
のコードアドレスを直接取得し、それをmakeFuncImpl
のcode
フィールドに設定するようになりました。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.typ
とctxt.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
関数を呼び出します。これにより、callReflect
は makeFuncImpl
から必要な情報(元の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 で導入された新しい関数呼び出し規約に準拠した、よりクリーンで効率的な実装へと進化しました。
関連リンク
- GitHub Commit: reflect: stop using run-time code generation
- Go 1.1 Function Calls (golang.org/s/go11func)
- Issue #3736: reflect: MakeFunc is slow
- Issue #3738: reflect: MakeFunc needs to be able to return a closure
- Issue #4081: reflect: MakeFunc needs to be able to return a function with a receiver
- Gerrit Change-Id: 7393050
参考にした情報源リンク
- Go 1.1 Function Calls (golang.org/s/go11func)
- Go Issues on GitHub
- Go reflect package documentation
- Go Assembly Language
- Go 1.1 Release Notes
- The Go Programming Language Specification
- Go's runtime and calling conventions (外部ブログ記事、Goの呼び出し規約の歴史的背景理解に役立つ)
- Go's reflect package and runtime code generation (外部ブログ記事、変更前の状況理解に役立つ)# [インデックス 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
は実行時に特定のアーキテクチャ向けのアセンブリコードを動的に生成し、そのコードを呼び出すことでリフレクションによる関数呼び出しを実現していました。
しかし、この動的なコード生成はいくつかの問題を引き起こしていました。
- 複雑性: 各アーキテクチャ(amd64, 386, armなど)ごとに異なるアセンブリコードテンプレートを管理する必要があり、コードベースが複雑化していました。
- セキュリティ: 実行時にコードを生成し実行することは、セキュリティ上の懸念(例えば、コードインジェクション攻撃のリスク)を増大させる可能性があります。
- パフォーマンス: 動的なコード生成とキャッシュフラッシュのオーバーヘッドがありました。
- 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つのポインタのペアになりました。
- コードポインタ: 実際の関数の機械語コードの開始アドレスを指します。
- コンテキストポインタ (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 の新しい関数呼び出し規約を利用します。
-
makeFuncImpl
構造体の変更:- 以前は
codeptr unsafe.Pointer
とcode [40]byte
を持っており、動的に生成されたアセンブリコードを保持していました。 - 変更後、これらは削除され、代わりに
code uintptr
が追加されました。このcode
フィールドは、静的に定義されたアセンブリ関数makeFuncStub
のエントリポイントを指します。 typ *rtype
は*funcType
に変更され、より具体的な関数型を指すようになりました。fn func([]Value) []Value
は、リフレクション呼び出しの際に実際に実行されるGo関数です。
- 以前は
-
MakeFunc
の実装変更:MakeFunc
は、makeFuncStub
のコードアドレスをdummy := makeFuncStub
のようにして取得し、それをmakeFuncImpl
のcode
フィールドに設定します。makeFuncImpl
のインスタンスが、生成されるreflect.Value
の内部データとして使用されます。このmakeFuncImpl
インスタンス自体が、Go 1.1 の新しい関数呼び出し規約における「コンテキストポインタ」の役割を果たします。
-
makeFuncStub
アセンブリ関数の変更:makeFuncStub
は、以前は動的に生成されたコードからジャンプされてくることを想定し、AX
,BX
,CX
(amd64/386) やR0
,R1
,R2
(arm) レジスタにtype
,fn
,frame
を受け取っていました。- 変更後、
makeFuncStub
はGo 1.1 の新しい関数呼び出し規約に従い、コンテキストレジスタ(アーキテクチャによって異なる)に*makeFuncImpl
のポインタを受け取るように変更されました。 makeFuncStub
の役割は、受け取ったmakeFuncImpl
のポインタからtyp
とfn
を抽出し、引数フレームのポインタと共にcallReflect
関数を呼び出すことです。
-
callReflect
関数の変更:- 以前は
func callReflect(ftyp *funcType, f func([]Value) []Value, frame unsafe.Pointer)
というシグネチャでした。 - 変更後、
func callReflect(ctxt *makeFuncImpl, frame unsafe.Pointer)
となり、makeFuncImpl
のポインタを直接受け取るようになりました。 callReflect
の内部で、ctxt.typ
とctxt.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.Pointer
とcode [40]byte
が削除され、code uintptr
が追加されました。typ *rtype
がtyp *funcType
に変更されました。MakeFunc
関数内の動的なアセンブリコード生成ロジック(switch runtime.GOARCH
ブロック)が完全に削除されました。MakeFunc
は、makeFuncStub
のコードアドレスを直接取得し、それをmakeFuncImpl
のcode
フィールドに設定するようになりました。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.typ
とctxt.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
関数を呼び出します。これにより、callReflect
は makeFuncImpl
から必要な情報(元の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 で導入された新しい関数呼び出し規約に準拠した、よりクリーンで効率的な実装へと進化しました。
関連リンク
- GitHub Commit: reflect: stop using run-time code generation
- Go 1.1 Function Calls (golang.org/s/go11func)
- Issue #3736: reflect: MakeFunc is slow
- Issue #3738: reflect: MakeFunc needs to be able to return a closure
- Issue #4081: reflect: MakeFunc needs to be able to return a function with a receiver
- Gerrit Change-Id: 7393050
参考にした情報源リンク
- Go 1.1 Function Calls (golang.org/s/go11func)
- Go Issues on GitHub
- Go reflect package documentation
- Go Assembly Language
- Go 1.1 Release Notes
- The Go Programming Language Specification
- Go's runtime and calling conventions (外部ブログ記事、Goの呼び出し規約の歴史的背景理解に役立つ)
- Go's reflect package and runtime code generation (外部ブログ記事、変更前の状況理解に役立つ)