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

[インデックス 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における関数呼び出し規約の変更に関する設計ドキュメントを指しています。このドキュメントの目的は、以下の点を改善することでした。

  1. クロージャの効率的なサポート: クロージャは、その定義されたスコープ外の変数を参照できる関数です。これを実現するためには、関数ポインタだけでなく、参照する変数へのポインタ(環境ポインタ)も関数値に含める必要があります。Go 1.0の直接的な関数ポインタ表現では、この環境ポインタを効率的に管理することが困難でした。
  2. reflect.MakeFuncの柔軟性向上: reflect.MakeFuncは、Goの型システムを介して動的に関数を作成する機能を提供します。この機能もまた、動的に生成される関数のための追加情報(例えば、引数の型情報や実際の呼び出しロジック)を関数値に含める必要があります。
  3. 関数呼び出しの最適化: 間接的な表現を導入することで、コンパイラとランタイムが関数呼び出しをより柔軟に最適化できるようになります。例えば、特定の条件下では直接呼び出しにフォールバックしたり、より複雑な呼び出し規約をサポートしたりすることが可能になります。

このコミットは、上記の目標を達成するための「ステップ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 (クロージャの環境ポインタなど)
};

この変更により、以下の技術的な影響があります。

  1. コンパイラ (cmd/gc) の変更:

    • 関数リテラル(匿名関数)や、reflectパッケージを通じて生成される関数が、直接的な関数ポインタではなく、FuncVal構造体へのポインタを生成するように変更されます。
    • 関数呼び出しのコード生成ロジックが変更され、FuncValから実際の関数エントリポイントを間接的に取得して呼び出すようになります。特に、ginscall関数(Goの関数呼び出しを生成する)やcgen_callinter関数(インターフェースメソッド呼び出しを生成する)が修正されています。
    • 新しいヘルパー関数funcsymが導入され、関数シンボルから対応するFuncValシンボルを生成します。これは、runtime·main·fのようなシンボルが生成される理由です。
  2. reflectパッケージの変更:

    • reflect.MakeFuncが、動的に生成する関数の内部表現としてFuncValを使用するように変更されます。これにより、クロージャの環境や、MakeFuncが提供するカスタムロジックをFuncValの「variable-size, fn-specific data」部分に格納できるようになります。
    • Value.Pointer()メソッドの挙動が変更され、関数値の場合にはFuncVal構造体の先頭(実際のコードポインタ)を返すようになります。
    • インターフェースメソッドや構造体メソッドの呼び出しにおいて、実際の関数ポインタをFuncValとして扱うように変更されます。
  3. ランタイム (runtime) の変更:

    • Goのスケジューラ、ゴルーチン生成、デファースタック、ファイナライザなど、関数ポインタを扱うすべての部分がFuncVal構造体を使用するように更新されます。
    • 特に、アセンブリコード(asm_386.s, asm_amd64.s, asm_arm.s)が、関数呼び出し時にFuncValから実際の関数エントリポイントをロードするように変更されます。例えば、runtime·gogocallfnのような新しいアセンブリ関数が導入され、FuncValを引数として受け取るようになります。
    • G(ゴルーチン)構造体のentryフィールドがfnstartFuncVal*型)に変更され、ゴルーチンの開始関数がFuncValとして保持されるようになります。
    • Defer構造体やTimer構造体も、関数ポインタをFuncVal*として保持するように変更されます。
    • Cgoコールバックの処理もFuncValを使用するように変更され、reflect·call関数もFuncValを引数として受け取るようになります。

この間接化により、Goの関数値はよりリッチな情報を保持できるようになり、クロージャやreflectによる動的な関数生成がより自然かつ効率的にサポートされるようになります。また、将来的な最適化や、より複雑な関数呼び出し規約の導入への道を開くことにもなります。

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

このコミットにおけるコアとなるコードの変更箇所は多岐にわたりますが、特に重要なのは以下のファイルとコードスニペットです。

  1. 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;
    
  2. 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;
    }
    
  3. 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;
    
  4. src/pkg/reflect/makefunc.go: MakeFuncFuncValを使用するように変更され、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}
     }
    
  5. 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構造体のアドレスを取得し、そのFuncValfnフィールド(実際の関数エントリポイント)を間接的にロードしてから呼び出すようになります。これにより、すべてのGoの関数呼び出しがFuncValを介して行われるようになり、クロージャなどの複雑な関数値も統一的に扱えるようになります。proc=3のケースは、C言語の関数ポインタを直接呼び出すためのパスであり、GoのFuncValとは異なる扱いをしています。

  • reflect.MakeFuncの変更: reflect.MakeFuncは、Goの型システムに基づいて動的に関数を作成します。この変更により、MakeFuncは、生成された関数をmakeFuncImplという内部構造体でラップし、そのmakeFuncImpl構造体へのポインタをValuevalフィールドに格納するようになります。makeFuncImplは、FuncValの概念を具体化したものであり、実際のコードポインタ(codeptr)と、MakeFuncに渡されたカスタムロジック(fn)を保持します。これにより、reflectパッケージは、Goの新しい関数値表現に完全に準拠し、動的な関数生成の柔軟性を維持できるようになります。

  • アセンブリコードの変更: ランタイムのアセンブリコードは、Goプログラムの低レベルな動作を制御します。このコミットでは、runtime·mainのようなGoの主要な関数の開始点も、直接的な関数ポインタではなく、runtime·main·fというFuncValを介して参照されるように変更されています。これは、Goプログラムのエントリポイントから、すべての関数呼び出しが新しいFuncValベースの規約に従うことを意味します。gogocallfnのような新しいアセンブリ関数は、FuncValを受け取り、そのfnフィールドを介して実際の関数を呼び出すための低レベルなロジックを提供します。

これらの変更は、Goの関数値が単なるコードへのポインタから、よりリッチなメタデータと実行時コンテキストを保持できるデータ構造へと進化する重要な一歩です。これにより、Go言語はクロージャやリフレクションといった高度な機能をより効率的かつ堅牢にサポートできるようになりました。

関連リンク

参考にした情報源リンク

  • 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における関数呼び出し規約の変更に関する設計ドキュメントを指しています。このドキュメントの目的は、以下の点を改善することでした。

  1. クロージャの効率的なサポート: クロージャは、その定義されたスコープ外の変数を参照できる関数です。これを実現するためには、関数ポインタだけでなく、参照する変数へのポインタ(環境ポインタ)も関数値に含める必要があります。Go 1.0の直接的な関数ポインタ表現では、この環境ポインタを効率的に管理することが困難でした。
  2. reflect.MakeFuncの柔軟性向上: reflect.MakeFuncは、Goの型システムを介して動的に関数を作成する機能を提供します。この機能もまた、動的に生成される関数のための追加情報(例えば、引数の型情報や実際の呼び出しロジック)を関数値に含める必要があります。
  3. 関数呼び出しの最適化: 間接的な表現を導入することで、コンパイラとランタイムが関数呼び出しをより柔軟に最適化できるようになります。例えば、特定の条件下では直接呼び出しにフォールバックしたり、より複雑な呼び出し規約をサポートしたりすることが可能になります。

このコミットは、上記の目標を達成するための「ステップ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 (クロージャの環境ポインタなど)
};

この変更により、以下の技術的な影響があります。

  1. コンパイラ (cmd/gc) の変更:

    • 関数リテラル(匿名関数)や、reflectパッケージを通じて生成される関数が、直接的な関数ポインタではなく、FuncVal構造体へのポインタを生成するように変更されます。
    • 関数呼び出しのコード生成ロジックが変更され、FuncValから実際の関数エントリポイントを間接的に取得して呼び出すようになります。特に、ginscall関数(Goの関数呼び出しを生成する)やcgen_callinter関数(インターフェースメソッド呼び出しを生成する)が修正されています。
    • 新しいヘルパー関数funcsymが導入され、関数シンボルから対応するFuncValシンボルを生成します。これは、runtime·main·fのようなシンボルが生成される理由です。
  2. reflectパッケージの変更:

    • reflect.MakeFuncが、動的に生成する関数の内部表現としてFuncValを使用するように変更されます。これにより、クロージャの環境や、MakeFuncが提供するカスタムロジックをFuncValの「variable-size, fn-specific data」部分に格納できるようになります。
    • Value.Pointer()メソッドの挙動が変更され、関数値の場合にはFuncVal構造体の先頭(実際のコードポインタ)を返すようになります。
    • インターフェースメソッドや構造体メソッドの呼び出しにおいて、実際の関数ポインタをFuncValとして扱うように変更されます。
  3. ランタイム (runtime) の変更:

    • Goのスケジューラ、ゴルーチン生成、デファースタック、ファイナライザなど、関数ポインタを扱うすべての部分がFuncVal構造体を使用するように更新されます。
    • 特に、アセンブリコード(asm_386.s, asm_amd64.s, asm_arm.s)が、関数呼び出し時にFuncValから実際の関数エントリポイントをロードするように変更されます。例えば、runtime·gogocallfnのような新しいアセンブリ関数が導入され、FuncValを引数として受け取るようになります。
    • G(ゴルーチン)構造体のentryフィールドがfnstartFuncVal*型)に変更され、ゴルーチンの開始関数がFuncValとして保持されるようになります。
    • Defer構造体やTimer構造体も、関数ポインタをFuncVal*として保持するように変更されます。
    • Cgoコールバックの処理もFuncValを使用するように変更され、reflect·call関数もFuncValを引数として受け取るようになります。

この間接化により、Goの関数値はよりリッチな情報を保持できるようになり、クロージャやreflectによる動的な関数生成がより自然かつ効率的にサポートされるようになります。また、将来的な最適化や、より複雑な関数呼び出し規約の導入への道を開くことにもなります。

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

このコミットにおけるコアとなるコードの変更箇所は多岐にわたりますが、特に重要なのは以下のファイルとコードスニペットです。

  1. 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;
    
  2. 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;
    }
    
  3. 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;
    
  4. src/pkg/reflect/makefunc.go: MakeFuncFuncValを使用するように変更され、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}
     }
    
  5. 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構造体のアドレスを取得し、そのFuncValfnフィールド(実際の関数エントリポイント)を間接的にロードしてから呼び出すようになります。これにより、すべてのGoの関数呼び出しがFuncValを介して行われるようになり、クロージャなどの複雑な関数値も統一的に扱えるようになります。proc=3のケースは、C言語の関数ポインタを直接呼び出すためのパスであり、GoのFuncValとは異なる扱いをしています。

  • reflect.MakeFuncの変更: reflect.MakeFuncは、Goの型システムに基づいて動的に関数を作成します。この変更により、MakeFuncは、生成された関数をmakeFuncImplという内部構造体でラップし、そのmakeFuncImpl構造体へのポインタをValuevalフィールドに格納するようになります。makeFuncImplは、FuncValの概念を具体化したものであり、実際のコードポインタ(codeptr)と、MakeFuncに渡されたカスタムロジック(fn)を保持します。これにより、reflectパッケージは、Goの新しい関数値表現に完全に準拠し、動的な関数生成の柔軟性を維持できるようになります。

  • アセンブリコードの変更: ランタイムのアセンブリコードは、Goプログラムの低レベルな動作を制御します。このコミットでは、runtime·mainのようなGoの主要な関数の開始点も、直接的な関数ポインタではなく、runtime·main·fというFuncValを介して参照されるように変更されています。これは、Goプログラムのエントリポイントから、すべての関数呼び出しが新しいFuncValベースの規約に従うことを意味します。gogocallfnのような新しいアセンブリ関数は、FuncValを受け取り、そのfnフィールドを介して実際の関数を呼び出すための低レベルなロジックを提供します。

これらの変更は、Goの関数値が単なるコードへのポインタから、よりリッチなメタデータと実行時コンテキストを保持できるデータ構造へと進化する重要な一歩です。これにより、Go言語はクロージャやリフレクションといった高度な機能をより効率的かつ堅牢にサポートできるようになりました。

関連リンク

参考にした情報源リンク

  • 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言語のコミット履歴: 関連するコミットや、この変更が導入された時期の他の変更を把握するために参照されました。