[インデックス 15363] ファイルの概要
このコミットは、Go言語のコンパイラ(cmd/gc
)、reflect
パッケージ、およびruntime
における関数値の表現方法を、直接参照から間接参照へと変更するものです。これは、Go 1.1における関数呼び出しの最適化と、より柔軟な関数表現を可能にするための第一段階として位置づけられています。
コミット
commit 1903ad71891eb0b7b79b83145bf16b4a85dead54
Author: Russ Cox <rsc@golang.org>
Date: Thu Feb 21 17:01:13 2013 -0500
cmd/gc, reflect, runtime: switch to indirect func value representation
Step 1 of http://golang.org/s/go11func.
R=golang-dev, r, daniel.morsing, remyoudompheng
CC=golang-dev
https://golang.org/cl/7393045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1903ad71891eb0b7b79b83145bf16b4a85dead54
元コミット内容
cmd/gc
, reflect
, runtime
: 関数値の表現を間接参照に切り替える。
これは http://golang.org/s/go11func
のステップ1である。
変更の背景
このコミットの背景には、Go言語の関数値(function value)の内部表現に関する重要な変更があります。Go 1.0では、関数値は直接的な関数ポインタとして表現されていました。しかし、この表現にはいくつかの制約がありました。特に、クロージャ(closure)やreflect.MakeFunc
によって動的に生成される関数など、実行時に追加のコンテキスト情報(環境ポインタなど)を必要とする関数を効率的に扱うことが困難でした。
コミットメッセージに記載されている http://golang.org/s/go11func
は、Go 1.1における関数呼び出し規約の変更に関する設計ドキュメントを指しています。このドキュメントの目的は、以下の点を改善することでした。
- クロージャの効率的なサポート: クロージャは、その定義されたスコープ外の変数を参照できる関数です。これを実現するためには、関数ポインタだけでなく、参照する変数へのポインタ(環境ポインタ)も関数値に含める必要があります。Go 1.0の直接的な関数ポインタ表現では、この環境ポインタを効率的に管理することが困難でした。
reflect.MakeFunc
の柔軟性向上:reflect.MakeFunc
は、Goの型システムを介して動的に関数を作成する機能を提供します。この機能もまた、動的に生成される関数のための追加情報(例えば、引数の型情報や実際の呼び出しロジック)を関数値に含める必要があります。- 関数呼び出しの最適化: 間接的な表現を導入することで、コンパイラとランタイムが関数呼び出しをより柔軟に最適化できるようになります。例えば、特定の条件下では直接呼び出しにフォールバックしたり、より複雑な呼び出し規約をサポートしたりすることが可能になります。
このコミットは、上記の目標を達成するための「ステップ1」として、関数値の表現を「間接参照」に切り替えることを目的としています。これにより、関数ポインタと環境ポインタを組み合わせた新しいFuncVal
構造体を導入し、Goの関数呼び出しメカニズムの基盤を強化しています。
前提知識の解説
このコミットを理解するためには、以下のGo言語の内部構造と概念に関する知識が必要です。
- 関数値 (Function Value): Go言語において、関数は第一級オブジェクトであり、変数に代入したり、引数として渡したり、戻り値として返したりすることができます。このような関数を「関数値」と呼びます。
- 関数ポインタ: C言語などと同様に、Goの関数もメモリ上の特定のアドレスに配置されており、そのアドレスを指すポインタが存在します。
- クロージャ (Closure): クロージャは、関数が定義された環境(スコープ)を「記憶」し、その環境内の変数にアクセスできる関数です。Goでは、匿名関数がクロージャとして振る舞うことがよくあります。クロージャは、関数ポインタだけでなく、その環境へのポインタも保持する必要があります。
reflect
パッケージ:reflect
パッケージは、Goプログラムが実行時に自身の構造を検査し、操作するための機能を提供します。特にreflect.MakeFunc
は、実行時に新しい関数を作成する強力な機能です。- ランタイム (Runtime): Goのランタイムは、ガベージコレクション、ゴルーチン管理、スケジューリング、チャネル通信など、Goプログラムの実行をサポートする低レベルのシステムです。
- アセンブリ言語 (Assembly Language): Goコンパイラは、Goのソースコードを最終的に機械語に変換します。この過程で、特定の低レベルな操作(例えば、関数呼び出し規約の変更)はアセンブリ言語で実装されることがあります。このコミットでは、
runtime
パッケージ内のアセンブリコードが変更されています。 FuncVal
構造体: このコミットで導入される新しい構造体で、関数ポインタと、クロージャなどの追加情報(環境ポインタなど)を保持するための領域をカプセル化します。
技術的詳細
このコミットの核心は、Goの関数値の内部表現を、従来の直接的な関数ポインタから、FuncVal
という新しい構造体へのポインタに変更することです。
変更前(Go 1.0まで): 関数値は、直接的に実行可能なコードの開始アドレスを指すポインタでした。
変更後(このコミット以降):
関数値は、FuncVal
構造体へのポインタとなります。FuncVal
構造体は、少なくとも以下の情報を含みます。
// src/pkg/runtime/runtime.h
struct FuncVal
{
void (*fn)(void); // 実際の関数のエントリポイント
// variable-size, fn-specific data here (クロージャの環境ポインタなど)
};
この変更により、以下の技術的な影響があります。
-
コンパイラ (
cmd/gc
) の変更:- 関数リテラル(匿名関数)や、
reflect
パッケージを通じて生成される関数が、直接的な関数ポインタではなく、FuncVal
構造体へのポインタを生成するように変更されます。 - 関数呼び出しのコード生成ロジックが変更され、
FuncVal
から実際の関数エントリポイントを間接的に取得して呼び出すようになります。特に、ginscall
関数(Goの関数呼び出しを生成する)やcgen_callinter
関数(インターフェースメソッド呼び出しを生成する)が修正されています。 - 新しいヘルパー関数
funcsym
が導入され、関数シンボルから対応するFuncVal
シンボルを生成します。これは、runtime·main·f
のようなシンボルが生成される理由です。
- 関数リテラル(匿名関数)や、
-
reflect
パッケージの変更:reflect.MakeFunc
が、動的に生成する関数の内部表現としてFuncVal
を使用するように変更されます。これにより、クロージャの環境や、MakeFunc
が提供するカスタムロジックをFuncVal
の「variable-size, fn-specific data」部分に格納できるようになります。Value.Pointer()
メソッドの挙動が変更され、関数値の場合にはFuncVal
構造体の先頭(実際のコードポインタ)を返すようになります。- インターフェースメソッドや構造体メソッドの呼び出しにおいて、実際の関数ポインタを
FuncVal
として扱うように変更されます。
-
ランタイム (
runtime
) の変更:- Goのスケジューラ、ゴルーチン生成、デファースタック、ファイナライザなど、関数ポインタを扱うすべての部分が
FuncVal
構造体を使用するように更新されます。 - 特に、アセンブリコード(
asm_386.s
,asm_amd64.s
,asm_arm.s
)が、関数呼び出し時にFuncVal
から実際の関数エントリポイントをロードするように変更されます。例えば、runtime·gogocallfn
のような新しいアセンブリ関数が導入され、FuncVal
を引数として受け取るようになります。 G
(ゴルーチン)構造体のentry
フィールドがfnstart
(FuncVal*
型)に変更され、ゴルーチンの開始関数がFuncVal
として保持されるようになります。Defer
構造体やTimer
構造体も、関数ポインタをFuncVal*
として保持するように変更されます。- Cgoコールバックの処理も
FuncVal
を使用するように変更され、reflect·call
関数もFuncVal
を引数として受け取るようになります。
- Goのスケジューラ、ゴルーチン生成、デファースタック、ファイナライザなど、関数ポインタを扱うすべての部分が
この間接化により、Goの関数値はよりリッチな情報を保持できるようになり、クロージャやreflect
による動的な関数生成がより自然かつ効率的にサポートされるようになります。また、将来的な最適化や、より複雑な関数呼び出し規約の導入への道を開くことにもなります。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は多岐にわたりますが、特に重要なのは以下のファイルとコードスニペットです。
-
src/cmd/gc/go.h
:FuncVal
構造体の定義と、G
構造体におけるfnstart
フィールドの追加。--- a/src/cmd/gc/go.h +++ b/src/cmd/gc/go.h @@ -154,6 +155,11 @@ struct String byte* str; intgo len; }; +struct FuncVal +{ + void (*fn)(void); + // variable-size, fn-specific data here +}; struct Iface { Itab* tab; @@ -209,7 +215,7 @@ struct G uintptr gcsp; // if status==Gsyscall, gcsp = sched.sp to use during gc uintptr gcguard; // if status==Gsyscall, gcguard = stackguard to use during gc uintptr stack0; - byte* entry; // initial function + FuncVal* fnstart; // initial function G* alllink; // on allg void* param; // passed parameter on wakeup int16 status;
-
src/cmd/gc/dcl.c
:funcsym
関数の追加。これは、Goの関数シンボルから、対応するFuncVal
シンボルを生成するためのものです。// src/cmd/gc/dcl.c Sym* funcsym(Sym *s) { char *p; Sym *s1; p = smprint("%s·f", s->name); // 例: runtime·main -> runtime·main·f s1 = pkglookup(p, s->pkg); free(p); if(s1->def == N) { s1->def = newname(s1); s1->def->shortname = newname(s); funcsyms = list(funcsyms, s1->def); } return s1; }
-
src/cmd/5g/ggen.c
,src/cmd/6g/ggen.c
,src/cmd/8g/ggen.c
(各アーキテクチャのコード生成):ginscall
関数における関数呼び出しロジックの変更。特に、PFUNC
(Goの関数)の場合と、C関数ポインタの場合で異なる処理を行うようになります。--- a/src/cmd/5g/ggen.c +++ b/src/cmd/5g/ggen.c @@ -68,10 +70,24 @@ ginscall(Node *f, int proc) case 0: // normal call case -1: // normal call but no return - p = gins(ABL, N, f); - afunclit(&p->to); - if(proc == -1 || noreturn(p)) - gins(AUNDEF, N, N); + if(f->op == ONAME && f->class == PFUNC) { + p = gins(ABL, N, f); + afunclit(&p->to, f); + if(proc == -1 || noreturn(p)) + gins(AUNDEF, N, N); + break; + } + nodreg(&r, types[tptr], 0); + nodreg(&r1, types[tptr], 1); + gmove(f, &r); + r.op = OINDREG; + gmove(&r, &r1); + r1.op = OINDREG; + gins(ABL, N, &r1); + break; + + case 3: // normal call of c function pointer + gins(ABL, N, f); break;
-
src/pkg/reflect/makefunc.go
:MakeFunc
がFuncVal
を使用するように変更され、Value
構造体のval
フィールドがmakeFuncImpl
構造体へのポインタを保持するようになります。--- a/src/pkg/reflect/makefunc.go +++ b/src/pkg/reflect/makefunc.go @@ -14,6 +14,8 @@ import ( // makeFuncImpl is the closure value implementing the function // returned by MakeFunc. type makeFuncImpl struct { + codeptr unsafe.Pointer + // References visible to the garbage collector. // The code array below contains the same references // embedded in the machine code. @@ -62,11 +64,12 @@ func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { typ: t, fn: fn, } + impl.codeptr = unsafe.Pointer(&impl.code[0]) tptr := unsafe.Pointer(t) fptr := *(*unsafe.Pointer)(unsafe.Pointer(&fn)) tmp := makeFuncStub - stub := *(*unsafe.Pointer)(unsafe.Pointer(&tmp)) + stub := **(**unsafe.Pointer)(unsafe.Pointer(&tmp)) // Create code. Copy template and fill in pointer values. switch runtime.GOARCH { @@ -95,5 +98,5 @@ func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { cacheflush(&impl.code[0], &impl.code[len(impl.code)-1]) } - return Value{t, unsafe.Pointer(&impl.code[0]), flag(Func) << flagKindShift} + return Value{t, unsafe.Pointer(impl), flag(Func) << flagKindShift} }
-
src/pkg/runtime/asm_*.s
(アセンブリファイル): 関数呼び出しのアセンブリコードが、FuncVal
から実際の関数エントリポイントをロードするように変更されます。例えば、runtime·main
の呼び出しがruntime·main·f
というFuncVal
を介して行われるようになります。--- a/src/pkg/runtime/asm_386.s +++ b/src/pkg/runtime/asm_386.s @@ -75,7 +75,7 @@ ok: CALL runtime·schedinit(SB) // create a new goroutine to start program - PUSHL $runtime·main(SB) // entry + PUSHL $runtime·main·f(SB) // entry PUSHL $0 // arg size CALL runtime·newproc(SB) POPL AX @@ -87,6 +87,9 @@ ok: INT $3 RET +DATA runtime·main·f+0(SB)/4,$runtime·main(SB) +GLOBL runtime·main·f(SB),8,$4 + TEXT runtime·breakpoint(SB),7,$0 INT $3 RET
コアとなるコードの解説
上記の変更箇所は、Goの関数呼び出しメカニズムの根本的な変更を示しています。
-
FuncVal
構造体: この構造体は、Goの関数値が単なるコードポインタではなく、より複雑なデータ構造であることを明確に定義します。fn
フィールドは実際の関数のエントリポイントを指し、その後に続く「variable-size, fn-specific data」は、クロージャの環境ポインタやreflect.MakeFunc
によって動的に生成された関数のための追加情報など、関数固有のデータを格納するために使用されます。これにより、Goの関数値は、C言語の関数ポインタよりも強力で柔軟な概念となります。 -
funcsym
関数: この関数は、Goのコンパイラが内部的に使用するシンボル管理の一部です。従来の関数シンボル(例:runtime·main
)に対して、·f
というサフィックスを付加した新しいシンボル(例:runtime·main·f
)を生成します。この新しいシンボルは、FuncVal
構造体を表すものであり、そのデータセクションには実際の関数エントリポイントへのポインタが格納されます。これにより、コンパイラは関数を直接参照する代わりに、このFuncVal
シンボルを介して間接的に参照するようになります。 -
ginscall
関数の変更:ginscall
は、Goのソースコードから関数呼び出しのアセンブリコードを生成するコンパイラの重要な部分です。この変更により、Goの関数(PFUNC
)を呼び出す際には、まずFuncVal
構造体のアドレスを取得し、そのFuncVal
のfn
フィールド(実際の関数エントリポイント)を間接的にロードしてから呼び出すようになります。これにより、すべてのGoの関数呼び出しがFuncVal
を介して行われるようになり、クロージャなどの複雑な関数値も統一的に扱えるようになります。proc=3
のケースは、C言語の関数ポインタを直接呼び出すためのパスであり、GoのFuncVal
とは異なる扱いをしています。 -
reflect.MakeFunc
の変更:reflect.MakeFunc
は、Goの型システムに基づいて動的に関数を作成します。この変更により、MakeFunc
は、生成された関数をmakeFuncImpl
という内部構造体でラップし、そのmakeFuncImpl
構造体へのポインタをValue
のval
フィールドに格納するようになります。makeFuncImpl
は、FuncVal
の概念を具体化したものであり、実際のコードポインタ(codeptr
)と、MakeFunc
に渡されたカスタムロジック(fn
)を保持します。これにより、reflect
パッケージは、Goの新しい関数値表現に完全に準拠し、動的な関数生成の柔軟性を維持できるようになります。 -
アセンブリコードの変更: ランタイムのアセンブリコードは、Goプログラムの低レベルな動作を制御します。このコミットでは、
runtime·main
のようなGoの主要な関数の開始点も、直接的な関数ポインタではなく、runtime·main·f
というFuncVal
を介して参照されるように変更されています。これは、Goプログラムのエントリポイントから、すべての関数呼び出しが新しいFuncVal
ベースの規約に従うことを意味します。gogocallfn
のような新しいアセンブリ関数は、FuncVal
を受け取り、そのfn
フィールドを介して実際の関数を呼び出すための低レベルなロジックを提供します。
これらの変更は、Goの関数値が単なるコードへのポインタから、よりリッチなメタデータと実行時コンテキストを保持できるデータ構造へと進化する重要な一歩です。これにより、Go言語はクロージャやリフレクションといった高度な機能をより効率的かつ堅牢にサポートできるようになりました。
関連リンク
- Go 1.1 Release Notes (Functions): https://go.dev/doc/go1.1#functions
- Go 1.1 Release Notes (Reflect): https://go.dev/doc/go1.1#reflect
参考にした情報源リンク
http://golang.org/s/go11func
(Go 1.1 Function Call Convention Design Document): このURLは、Go 1.1の関数呼び出し規約の変更に関する設計ドキュメントを指しています。このコミットの直接的な背景と目的を理解するために不可欠な情報源です。- Go言語のソースコード (特に
src/cmd/gc
,src/pkg/reflect
,src/pkg/runtime
ディレクトリ): コミットの変更内容を詳細に分析するために、これらのディレクトリ内のファイルが参照されました。 - Go言語の公式ドキュメント:
reflect
パッケージやランタイムに関する一般的な理解のために参照されました。 - Go言語のコミット履歴: 関連するコミットや、この変更が導入された時期の他の変更を把握するために参照されました。
[インデックス 15363] ファイルの概要
このコミットは、Go言語のコンパイラ(cmd/gc
)、reflect
パッケージ、およびruntime
における関数値の表現方法を、直接参照から間接参照へと変更するものです。これは、Go 1.1における関数呼び出しの最適化と、より柔軟な関数表現を可能にするための第一段階として位置づけられています。
コミット
commit 1903ad71891eb0b7b79b83145bf16b4a85dead54
Author: Russ Cox <rsc@golang.org>
Date: Thu Feb 21 17:01:13 2013 -0500
cmd/gc, reflect, runtime: switch to indirect func value representation
Step 1 of http://golang.org/s/go11func.
R=golang-dev, r, daniel.morsing, remyoudompheng
CC=golang-dev
https://golang.org/cl/7393045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1903ad71891eb0b7b79b83145bf16b4a85dead54
元コミット内容
cmd/gc
, reflect
, runtime
: 関数値の表現を間接参照に切り替える。
これは http://golang.org/s/go11func
のステップ1である。
変更の背景
このコミットの背景には、Go言語の関数値(function value)の内部表現に関する重要な変更があります。Go 1.0では、関数値は直接的な関数ポインタとして表現されていました。しかし、この表現にはいくつかの制約がありました。特に、クロージャ(closure)やreflect.MakeFunc
によって動的に生成される関数など、実行時に追加のコンテキスト情報(環境ポインタなど)を必要とする関数を効率的に扱うことが困難でした。
コミットメッセージに記載されている http://golang.org/s/go11func
は、Go 1.1における関数呼び出し規約の変更に関する設計ドキュメントを指しています。このドキュメントの目的は、以下の点を改善することでした。
- クロージャの効率的なサポート: クロージャは、その定義されたスコープ外の変数を参照できる関数です。これを実現するためには、関数ポインタだけでなく、参照する変数へのポインタ(環境ポインタ)も関数値に含める必要があります。Go 1.0の直接的な関数ポインタ表現では、この環境ポインタを効率的に管理することが困難でした。
reflect.MakeFunc
の柔軟性向上:reflect.MakeFunc
は、Goの型システムを介して動的に関数を作成する機能を提供します。この機能もまた、動的に生成される関数のための追加情報(例えば、引数の型情報や実際の呼び出しロジック)を関数値に含める必要があります。- 関数呼び出しの最適化: 間接的な表現を導入することで、コンパイラとランタイムが関数呼び出しをより柔軟に最適化できるようになります。例えば、特定の条件下では直接呼び出しにフォールバックしたり、より複雑な呼び出し規約をサポートしたりすることが可能になります。
このコミットは、上記の目標を達成するための「ステップ1」として、関数値の表現を「間接参照」に切り替えることを目的としています。これにより、関数ポインタと環境ポインタを組み合わせた新しいFuncVal
構造体を導入し、Goの関数呼び出しメカニズムの基盤を強化しています。
前提知識の解説
このコミットを理解するためには、以下のGo言語の内部構造と概念に関する知識が必要です。
- 関数値 (Function Value): Go言語において、関数は第一級オブジェクトであり、変数に代入したり、引数として渡したり、戻り値として返したりすることができます。このような関数を「関数値」と呼びます。
- 関数ポインタ: C言語などと同様に、Goの関数もメモリ上の特定のアドレスに配置されており、そのアドレスを指すポインタが存在します。
- クロージャ (Closure): クロージャは、関数が定義された環境(スコープ)を「記憶」し、その環境内の変数にアクセスできる関数です。Goでは、匿名関数がクロージャとして振る舞うことがよくあります。クロージャは、関数ポインタだけでなく、その環境へのポインタも保持する必要があります。
reflect
パッケージ:reflect
パッケージは、Goプログラムが実行時に自身の構造を検査し、操作するための機能を提供します。特にreflect.MakeFunc
は、実行時に新しい関数を作成する強力な機能です。- ランタイム (Runtime): Goのランタイムは、ガベージコレクション、ゴルーチン管理、スケジューリング、チャネル通信など、Goプログラムの実行をサポートする低レベルのシステムです。
- アセンブリ言語 (Assembly Language): Goコンパイラは、Goのソースコードを最終的に機械語に変換します。この過程で、特定の低レベルな操作(例えば、関数呼び出し規約の変更)はアセンブリ言語で実装されることがあります。このコミットでは、
runtime
パッケージ内のアセンブリコードが変更されています。 FuncVal
構造体: このコミットで導入される新しい構造体で、関数ポインタと、クロージャなどの追加情報(環境ポインタなど)を保持するための領域をカプセル化します。
技術的詳細
このコミットの核心は、Goの関数値の内部表現を、従来の直接的な関数ポインタから、FuncVal
という新しい構造体へのポインタに変更することです。
変更前(Go 1.0まで): 関数値は、直接的に実行可能なコードの開始アドレスを指すポインタでした。
変更後(このコミット以降):
関数値は、FuncVal
構造体へのポインタとなります。FuncVal
構造体は、少なくとも以下の情報を含みます。
// src/pkg/runtime/runtime.h
struct FuncVal
{
void (*fn)(void); // 実際の関数のエントリポイント
// variable-size, fn-specific data here (クロージャの環境ポインタなど)
};
この変更により、以下の技術的な影響があります。
-
コンパイラ (
cmd/gc
) の変更:- 関数リテラル(匿名関数)や、
reflect
パッケージを通じて生成される関数が、直接的な関数ポインタではなく、FuncVal
構造体へのポインタを生成するように変更されます。 - 関数呼び出しのコード生成ロジックが変更され、
FuncVal
から実際の関数エントリポイントを間接的に取得して呼び出すようになります。特に、ginscall
関数(Goの関数呼び出しを生成する)やcgen_callinter
関数(インターフェースメソッド呼び出しを生成する)が修正されています。 - 新しいヘルパー関数
funcsym
が導入され、関数シンボルから対応するFuncVal
シンボルを生成します。これは、runtime·main·f
のようなシンボルが生成される理由です。
- 関数リテラル(匿名関数)や、
-
reflect
パッケージの変更:reflect.MakeFunc
が、動的に生成する関数の内部表現としてFuncVal
を使用するように変更されます。これにより、クロージャの環境や、MakeFunc
が提供するカスタムロジックをFuncVal
の「variable-size, fn-specific data」部分に格納できるようになります。Value.Pointer()
メソッドの挙動が変更され、関数値の場合にはFuncVal
構造体の先頭(実際のコードポインタ)を返すようになります。- インターフェースメソッドや構造体メソッドの呼び出しにおいて、実際の関数ポインタを
FuncVal
として扱うように変更されます。
-
ランタイム (
runtime
) の変更:- Goのスケジューラ、ゴルーチン生成、デファースタック、ファイナライザなど、関数ポインタを扱うすべての部分が
FuncVal
構造体を使用するように更新されます。 - 特に、アセンブリコード(
asm_386.s
,asm_amd64.s
,asm_arm.s
)が、関数呼び出し時にFuncVal
から実際の関数エントリポイントをロードするように変更されます。例えば、runtime·gogocallfn
のような新しいアセンブリ関数が導入され、FuncVal
を引数として受け取るようになります。 G
(ゴルーチン)構造体のentry
フィールドがfnstart
(FuncVal*
型)に変更され、ゴルーチンの開始関数がFuncVal
として保持されるようになります。Defer
構造体やTimer
構造体も、関数ポインタをFuncVal*
として保持するように変更されます。- Cgoコールバックの処理も
FuncVal
を使用するように変更され、reflect·call
関数もFuncVal
を引数として受け取るようになります。
- Goのスケジューラ、ゴルーチン生成、デファースタック、ファイナライザなど、関数ポインタを扱うすべての部分が
この間接化により、Goの関数値はよりリッチな情報を保持できるようになり、クロージャやreflect
による動的な関数生成がより自然かつ効率的にサポートされるようになります。また、将来的な最適化や、より複雑な関数呼び出し規約の導入への道を開くことにもなります。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は多岐にわたりますが、特に重要なのは以下のファイルとコードスニペットです。
-
src/cmd/gc/go.h
:FuncVal
構造体の定義と、G
構造体におけるfnstart
フィールドの追加。--- a/src/cmd/gc/go.h +++ b/src/cmd/gc/go.h @@ -154,6 +155,11 @@ struct String byte* str; intgo len; }; +struct FuncVal +{ + void (*fn)(void); + // variable-size, fn-specific data here +}; struct Iface { Itab* tab; @@ -209,7 +215,7 @@ struct G uintptr gcsp; // if status==Gsyscall, gcsp = sched.sp to use during gc uintptr gcguard; // if status==Gsyscall, gcguard = stackguard to use during gc uintptr stack0; - byte* entry; // initial function + FuncVal* fnstart; // initial function G* alllink; // on allg void* param; // passed parameter on wakeup int16 status;
-
src/cmd/gc/dcl.c
:funcsym
関数の追加。これは、Goの関数シンボルから、対応するFuncVal
シンボルを生成するためのものです。// src/cmd/gc/dcl.c Sym* funcsym(Sym *s) { char *p; Sym *s1; p = smprint("%s·f", s->name); // 例: runtime·main -> runtime·main·f s1 = pkglookup(p, s->pkg); free(p); if(s1->def == N) { s1->def = newname(s1); s1->def->shortname = newname(s); funcsyms = list(funcsyms, s1->def); } return s1; }
-
src/cmd/5g/ggen.c
,src/cmd/6g/ggen.c
,src/cmd/8g/ggen.c
(各アーキテクチャのコード生成):ginscall
関数における関数呼び出しロジックの変更。特に、PFUNC
(Goの関数)の場合と、C関数ポインタの場合で異なる処理を行うようになります。--- a/src/cmd/5g/ggen.c +++ b/src/cmd/5g/ggen.c @@ -68,10 +70,24 @@ ginscall(Node *f, int proc) case 0: // normal call case -1: // normal call but no return - p = gins(ABL, N, f); - afunclit(&p->to); - if(proc == -1 || noreturn(p)) - gins(AUNDEF, N, N); + if(f->op == ONAME && f->class == PFUNC) { + p = gins(ABL, N, f); + afunclit(&p->to, f); + if(proc == -1 || noreturn(p)) + gins(AUNDEF, N, N); + break; + } + nodreg(&r, types[tptr], 0); + nodreg(&r1, types[tptr], 1); + gmove(f, &r); + r.op = OINDREG; + gmove(&r, &r1); + r1.op = OINDREG; + gins(ABL, N, &r1); + break; + + case 3: // normal call of c function pointer + gins(ABL, N, f); break;
-
src/pkg/reflect/makefunc.go
:MakeFunc
がFuncVal
を使用するように変更され、Value
構造体のval
フィールドがmakeFuncImpl
構造体へのポインタを保持するようになります。--- a/src/pkg/reflect/makefunc.go +++ b/src/pkg/reflect/makefunc.go @@ -14,6 +14,8 @@ import ( // makeFuncImpl is the closure value implementing the function // returned by MakeFunc. type makeFuncImpl struct { + codeptr unsafe.Pointer + // References visible to the garbage collector. // The code array below contains the same references // embedded in the machine code. @@ -62,11 +64,12 @@ func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { typ: t, fn: fn, } + impl.codeptr = unsafe.Pointer(&impl.code[0]) tptr := unsafe.Pointer(t) fptr := *(*unsafe.Pointer)(unsafe.Pointer(&fn)) tmp := makeFuncStub - stub := *(*unsafe.Pointer)(unsafe.Pointer(&tmp)) + stub := **(**unsafe.Pointer)(unsafe.Pointer(&tmp)) // Create code. Copy template and fill in pointer values. switch runtime.GOARCH { @@ -95,5 +98,5 @@ func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { cacheflush(&impl.code[0], &impl.code[len(impl.code)-1]) } - return Value{t, unsafe.Pointer(&impl.code[0]), flag(Func) << flagKindShift} + return Value{t, unsafe.Pointer(impl), flag(Func) << flagKindShift} }
-
src/pkg/runtime/asm_*.s
(アセンブリファイル): 関数呼び出しのアセンブリコードが、FuncVal
から実際の関数エントリポイントをロードするように変更されます。例えば、runtime·main
の呼び出しがruntime·main·f
というFuncVal
を介して行われるようになります。--- a/src/pkg/runtime/asm_386.s +++ b/src/pkg/runtime/asm_386.s @@ -75,7 +75,7 @@ ok: CALL runtime·schedinit(SB) // create a new goroutine to start program - PUSHL $runtime·main(SB) // entry + PUSHL $runtime·main·f(SB) // entry PUSHL $0 // arg size CALL runtime·newproc(SB) POPL AX @@ -87,6 +87,9 @@ ok: INT $3 RET +DATA runtime·main·f+0(SB)/4,$runtime·main(SB) +GLOBL runtime·main·f(SB),8,$4 + TEXT runtime·breakpoint(SB),7,$0 INT $3 RET
コアとなるコードの解説
上記の変更箇所は、Goの関数呼び出しメカニズムの根本的な変更を示しています。
-
FuncVal
構造体: この構造体は、Goの関数値が単なるコードポインタではなく、より複雑なデータ構造であることを明確に定義します。fn
フィールドは実際の関数のエントリポイントを指し、その後に続く「variable-size, fn-specific data」は、クロージャの環境ポインタやreflect.MakeFunc
によって動的に生成された関数のための追加情報など、関数固有のデータを格納するために使用されます。これにより、Goの関数値は、C言語の関数ポインタよりも強力で柔軟な概念となります。 -
funcsym
関数: この関数は、Goのコンパイラが内部的に使用するシンボル管理の一部です。従来の関数シンボル(例:runtime·main
)に対して、·f
というサフィックスを付加した新しいシンボル(例:runtime·main·f
)を生成します。この新しいシンボルは、FuncVal
構造体を表すものであり、そのデータセクションには実際の関数エントリポイントへのポインタが格納されます。これにより、コンパイラは関数を直接参照する代わりに、このFuncVal
シンボルを介して間接的に参照するようになります。 -
ginscall
関数の変更:ginscall
は、Goのソースコードから関数呼び出しのアセンブリコードを生成するコンパイラの重要な部分です。この変更により、Goの関数(PFUNC
)を呼び出す際には、まずFuncVal
構造体のアドレスを取得し、そのFuncVal
のfn
フィールド(実際の関数エントリポイント)を間接的にロードしてから呼び出すようになります。これにより、すべてのGoの関数呼び出しがFuncVal
を介して行われるようになり、クロージャなどの複雑な関数値も統一的に扱えるようになります。proc=3
のケースは、C言語の関数ポインタを直接呼び出すためのパスであり、GoのFuncVal
とは異なる扱いをしています。 -
reflect.MakeFunc
の変更:reflect.MakeFunc
は、Goの型システムに基づいて動的に関数を作成します。この変更により、MakeFunc
は、生成された関数をmakeFuncImpl
という内部構造体でラップし、そのmakeFuncImpl
構造体へのポインタをValue
のval
フィールドに格納するようになります。makeFuncImpl
は、FuncVal
の概念を具体化したものであり、実際のコードポインタ(codeptr
)と、MakeFunc
に渡されたカスタムロジック(fn
)を保持します。これにより、reflect
パッケージは、Goの新しい関数値表現に完全に準拠し、動的な関数生成の柔軟性を維持できるようになります。 -
アセンブリコードの変更: ランタイムのアセンブリコードは、Goプログラムの低レベルな動作を制御します。このコミットでは、
runtime·main
のようなGoの主要な関数の開始点も、直接的な関数ポインタではなく、runtime·main·f
というFuncVal
を介して参照されるように変更されています。これは、Goプログラムのエントリポイントから、すべての関数呼び出しが新しいFuncVal
ベースの規約に従うことを意味します。gogocallfn
のような新しいアセンブリ関数は、FuncVal
を受け取り、そのfn
フィールドを介して実際の関数を呼び出すための低レベルなロジックを提供します。
これらの変更は、Goの関数値が単なるコードへのポインタから、よりリッチなメタデータと実行時コンテキストを保持できるデータ構造へと進化する重要な一歩です。これにより、Go言語はクロージャやリフレクションといった高度な機能をより効率的かつ堅牢にサポートできるようになりました。
関連リンク
- Go 1.1 Release Notes (Functions): https://go.dev/doc/go1.1#functions
- Go 1.1 Release Notes (Reflect): https://go.dev/doc/go1.1#reflect
参考にした情報源リンク
http://golang.org/s/go11func
(Go 1.1 Function Call Convention Design Document): このURLは、Go 1.1の関数呼び出し規約の変更に関する設計ドキュメントを指しています。このコミットの直接的な背景と目的を理解するために不可欠な情報源です。- Go言語のソースコード (特に
src/cmd/gc
,src/pkg/reflect
,src/pkg/runtime
ディレクトリ): コミットの変更内容を詳細に分析するために、これらのディレクトリ内のファイルが参照されました。 - Go言語の公式ドキュメント:
reflect
パッケージやランタイムに関する一般的な理解のために参照されました。 - Go言語のコミット履歴: 関連するコミットや、この変更が導入された時期の他の変更を把握するために参照されました。