[インデックス 16867] ファイルの概要
このコミットは、GoランタイムにおけるC関数呼び出し時のポインタマップ生成に関する変更を取り消すものです。具体的には、以前のコミット (CL 11683043 / bb75d03e6ccb) で導入された、C関数の引数ポインタマップを生成するロジックが、ARMアーキテクチャでのビルドを壊したため、その変更を元に戻しています。
コミット
commit b3defa2e8e66f5a03ce4ac344b9e5bcf3b4321d3
Author: Keith Randall <khr@golang.org>
Date: Wed Jul 24 15:04:10 2013 -0700
undo CL 11683043 / bb75d03e6ccb
Broke arm build.
R=dave
««« original CL description
cc: generate argument pointer maps for C functions.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/11683043
»»»
R=golang-dev
CC=golang-dev
https://golang.org/cl/11788043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b3defa2e8e66f5a03ce4ac344b9e5bcf3b4321d3
元コミット内容
このコミットは、以下の元のコミット(CL 11683043 / bb75d03e6ccb)の変更を取り消すものです。
cc: generate argument pointer maps for C functions.
元のコミットは、Goのコンパイラ(cc
)がC関数を呼び出す際に、その引数に含まれるポインタを追跡するための「ポインタマップ」を生成する機能を追加しようとしたものでした。これは、Goのガベージコレクション(GC)がCコードとの相互運用性を正しく処理するために必要となる情報です。
変更の背景
このコミットの背景には、GoのガベージコレクションとC言語との相互運用性における課題があります。Goは独自のガベージコレクタを持っており、ヒープ上のポインタを正確に識別し、到達可能なオブジェクトをマークすることでメモリを管理します。しかし、GoプログラムがC関数を呼び出す場合、C関数はGoのGCからは見えないメモリ領域(CスタックやCヒープ)にポインタを保持する可能性があります。
元のコミット(CL 11683043)は、この問題を解決するための一歩として、C関数に渡される引数の中にポインタが含まれている場合に、Goのランタイムがそのポインタの位置を把握できるようにするための「ポインタマップ」を生成しようとしました。これにより、GCがC関数呼び出し中にGoのヒープオブジェクトがCコードによって参照されていることを認識し、それらのオブジェクトが誤って回収されるのを防ぐことが期待されました。
しかし、この変更が導入された結果、「Broke arm build.」(ARMビルドを壊した)という問題が発生しました。これは、ARMアーキテクチャ特有のコンパイラやランタイムの挙動、あるいはメモリレイアウトの差異などにより、ポインタマップ生成ロジックが正しく機能しなかったか、予期せぬ副作用を引き起こしたためと考えられます。Goのクロスコンパイルとマルチアーキテクチャサポートは重要な機能であるため、特定のアーキテクチャでのビルドが壊れることは許容されません。そのため、問題の修正に時間がかかることを考慮し、一時的に元の変更を取り消す判断がなされました。
前提知識の解説
ガベージコレクション (GC) とポインタマップ
Go言語は、メモリ管理にガベージコレクション(GC)を採用しています。GCは、プログラムが動的に確保したメモリ領域のうち、もはやどの部分からも参照されていない(到達不可能になった)ものを自動的に解放する仕組みです。GoのGCは、主に「マーク&スイープ」方式をベースとしており、プログラムの実行中にヒープ上のオブジェクトを走査し、到達可能なオブジェクトをマークし、マークされなかったオブジェクトを解放します。
GCが正しく機能するためには、ヒープ上のどの値がポインタであるかを正確に識別する必要があります。なぜなら、ポインタだけが他のオブジェクトへの参照を保持し、GCの走査の起点となるからです。このポインタの識別を助けるのが「ポインタマップ」です。ポインタマップは、特定のメモリ領域(例えば、スタックフレームやヒープオブジェクトの構造体)内のどのオフセットにポインタが存在するかを示すビットマップやリストのようなデータ構造です。Goのランタイムは、このポインタマップを利用して、GCの走査時にポインタを効率的に見つけ出し、追跡します。
CgoとGo/C間の相互運用性
Go言語は、cgo
ツールを通じてC言語のコードと相互運用する機能を提供しています。これにより、Goプログラムから既存のCライブラリを呼び出したり、CコードからGoの関数を呼び出したりすることが可能になります。しかし、GoとCは異なるメモリ管理モデルとランタイムを持っています。
- Goのメモリ管理: GoのGCが管理するヒープと、Goルーチンのスタックがあります。
- Cのメモリ管理:
malloc
/free
による手動管理、およびC関数のスタックがあります。
GoのGCは、Goのヒープとスタック上のポインタは追跡できますが、CのスタックやCのヒープに存在するポインタは直接追跡できません。もしGoのヒープオブジェクトへのポインタがCのスタックやヒープに渡され、Cコードがそのポインタを保持している間にGoのGCが実行されると、GCはそのGoヒープオブジェクトがまだ参照されていることを認識できず、誤って解放してしまう可能性があります(Use-After-Freeバグ)。
この問題を解決するためには、GoのランタイムがC関数呼び出し中にC側のメモリに存在するGoヒープへのポインタを認識し、GCの対象から除外するか、GCの走査に含める必要があります。ポインタマップは、この情報を提供する重要なメカニズムの一つです。
src/cmd/cc/pgen.c
src/cmd/cc/pgen.c
は、Goのツールチェインの一部であるcc
コマンド(Cコンパイラ)のバックエンドに関連するファイルです。このファイルは、GoのコンパイラがCコードを処理し、Goランタイムとの連携に必要な情報を生成する役割を担っています。特に、関数呼び出し規約、スタックフレームのレイアウト、そしてガベージコレクションに関連するメタデータ(ポインタマップなど)の生成に関わっています。
技術的詳細
このコミットは、src/cmd/cc/pgen.c
ファイルから、C関数呼び出し時の引数ポインタマップを生成するためのロジックを削除しています。具体的には、以下の関数とそれに関連するコードが削除されました。
static int32 pointermap(Sym *gcsym, int32 offset);
static int32 pointermap_type(Type *t, int32 offset, int32 baseidx);
これらの関数は、C関数の引数リストを走査し、どのオフセットにポインタが存在するかを示すビットマップを構築することを目的としていました。
pointermap_type
関数
この関数は、与えられた型 t
とオフセット offset
に基づいて、ポインタマップのビットを計算していました。
TIND
(ポインタ型) やTARRAY
(Cでは配列はポインタとして渡される) の場合、指定されたオフセットがポインタのサイズ(ewidth[TIND]
)の倍数であることを確認し、対応するビットを設定していました。TSTRUCT
(構造体) の場合、構造体の各フィールドを再帰的に走査してポインタマップを構築していました。TUNION
(共用体) の場合、共用体のすべてのメンバーが同じポインタマップを持つことを要求していました。これは、共用体内のポインタの配置がメンバーによって異なる場合に、GCが混乱するのを防ぐためと考えられます。
pointermap
関数
この関数は、C関数の引数リスト全体に対してポインタマップを計算し、そのデータをgcsym
(ガベージコレクションシンボル)に格納していました。
hasdotdotdot()
(可変引数関数)の場合、ポインタマップの生成を諦め、nptrs=0
としていました。これは、可変引数関数の引数リストの解析が複雑であるため、初期段階ではサポートを見送ったものと考えられます。- 引数リストのサイズからポインタの数を計算し、
nptrs
として記録していました。 - 引数リストを走査し、
pointermap_type
を呼び出して各引数のポインタマップビットを計算し、それらを結合して最終的なポインタマップを生成していました。 - Cの呼び出し規約で、構造体が隠れた最初の引数(ポインタ)によって返される場合、そのポインタもマップに含めていました。
削除されたコードの影響
これらの関数が削除されたことにより、GoのコンパイラはC関数呼び出し時の引数に関するポインタマップ情報を生成しなくなりました。これは、GoのGCがC関数呼び出し中にGoヒープオブジェクトへの参照を正確に追跡できなくなることを意味します。結果として、CコードがGoヒープオブジェクトへのポインタを保持している間にGCが実行されると、そのオブジェクトが誤って解放されるリスクが生じます。
この変更がARMビルドを壊した具体的な原因はコミットメッセージからは不明ですが、一般的には以下のような理由が考えられます。
- アライメントの問題: ARMアーキテクチャは、特定のデータ型に対して厳密なアライメント要件を持つことがあります。ポインタマップの計算やメモリへの書き込みが、ARMの特定のアライメント要件を満たしていなかった可能性があります。
- ポインタサイズの差異: 32ビットARMと64ビットARMなど、ポインタのサイズが異なる環境での考慮不足があったかもしれません。
- スタックフレームのレイアウト: ARMの関数呼び出し規約やスタックフレームのレイアウトが、ポインタマップ生成ロジックの前提と異なっていた可能性があります。
- コンパイラのバグ:
src/cmd/cc
自体、あるいはGoのツールチェインの他の部分に、ARMアーキテクチャ特有のバグがあり、この新しいポインタマップ生成ロジックと組み合わさることで顕在化した可能性もあります。
このコミットは、機能の追加よりも安定性を優先し、問題のある変更を一時的にロールバックするという、ソフトウェア開発における一般的なプラクティスを示しています。
コアとなるコードの変更箇所
src/cmd/cc/pgen.c
ファイルにおいて、主に以下の変更が行われました。
-
pointermap
およびpointermap_type
関数の削除:static int32 pointermap(Sym *gcsym, int32 offset);
の前方宣言が削除されました。pointermap_type
関数(638行目から700行目)が完全に削除されました。pointermap
関数(703行目から757行目)が完全に削除されました。
-
codgen
関数内のポインタマップ生成ロジックの削除:- 元のコミットで追加された、
AFUNCDATA
命令を使ってGCシンボルにポインタマップデータを関連付ける部分が削除されました。具体的には、以下のコードブロックが削除されました。
/* * generate funcdata symbol for this function. * data is filled in at the end of codgen(). */ snprint(namebuf, sizeof namebuf, "gc·%d", ngcsym++); gcsym = slookup(namebuf); gcsym->class = CSTATIC; memset(&nod, 0, sizeof nod); nod.op = ONAME; nod.sym = gcsym; nod.class = CSTATIC; gins(AFUNCDATA, nodconst(FUNCDATA_GC), &nod);
- 代わりに、以前のコード(ポインタマップ生成ロジックが導入される前のコード)が復元されました。これは、
snprint
、slookup
、memset
、gins
を使ってFUNCDATA_GC
シンボルを生成する部分が、codgen
関数の別の場所(156行目以降)に移動されたことを意味します。
- 元のコミットで追加された、
-
gextern
呼び出しの変更:gextern(gcsym, nodconst(0), off, 4); // nptrs
の行が追加され、pointermap
関数が削除されたため、nptrs
(ポインタの数) を直接0として書き込むようになりました。off = pointermap(gcsym, off); // nptrs and ptrs[...]
の行が削除されました。
これらの変更は、C関数呼び出し時のポインタマップ生成に関連するすべてのコードパスを、src/cmd/cc/pgen.c
から完全に削除することを目的としています。
コアとなるコードの解説
このコミットの核心は、GoのコンパイラがC関数呼び出しの際に生成するメタデータから、ポインタマップに関する情報を削除した点にあります。
src/cmd/cc/pgen.c
は、GoのCコンパイラ(cmd/cc
)のバックエンドの一部であり、CソースコードをGoのオブジェクトファイル形式に変換する際に、Goランタイムが必要とする情報を埋め込む役割を担っています。特に、Goのガベージコレクション(GC)がC関数呼び出し中にGoヒープ上のオブジェクトを正しく追跡できるように、C関数の引数やローカル変数にGoヒープへのポインタが含まれている場合に、その位置を示すメタデータ(ポインタマップ)を生成することが求められます。
元のコミット(CL 11683043)では、codgen
関数内でAFUNCDATA
命令とFUNCDATA_GC
定数を使用して、C関数のポインタマップ情報をGoのオブジェクトファイルに埋め込もうとしていました。FUNCDATA_GC
は、GoランタイムがGC情報を取得するために使用する特別なデータセクションを指します。このデータセクションには、関数のスタックフレーム内のポインタの位置を示すビットマップなどが含まれます。
削除されたpointermap
関数とpointermap_type
関数は、このポインタマップの具体的なビットマップを計算するロジックを実装していました。pointermap_type
は、Cの型システム(構造体、ポインタ、配列など)を走査し、ポインタが存在するオフセットを特定してビットマップに変換します。pointermap
は、関数の引数リスト全体に対してこの処理を適用し、最終的なポインタマップを生成していました。
このコミットでは、これらのポインタマップ生成ロジックが完全に削除されました。これは、ARMビルドが壊れたという問題が、このポインタマップ生成ロジックのバグ、あるいはARMアーキテクチャ特有の制約との不整合に起因すると判断されたためです。ポインタマップの生成を停止することで、一時的にARMビルドの問題を回避し、Goのツールチェインの安定性を確保しました。
結果として、このコミットが適用された状態では、GoのGCはC関数呼び出し中にC側のスタックやメモリに存在するGoヒープへのポインタを正確に追跡できません。これは、Cgoを使用するGoプログラムにおいて、C関数がGoヒープオブジェクトへのポインタを保持している場合に、GCがそのオブジェクトを誤って回収してしまう可能性(Use-After-Free)があることを意味します。この問題は、その後のコミットでより堅牢な方法で解決されることになりますが、このコミットは、GoのGCとCgoの相互運用性における初期の課題と、特定のアーキテクチャでの安定性確保の重要性を示しています。
関連リンク
- 元のコミット (CL 11683043): https://golang.org/cl/11683043 (このコミットで取り消された変更)
- このコミット (CL 11788043): https://golang.org/cl/11788043
参考にした情報源リンク
- Go言語のガベージコレクションに関する公式ドキュメントやブログ記事 (GoのGCの仕組み、ポインタマップの概念について)
- Go言語の
cgo
に関する公式ドキュメント (GoとCの相互運用性、メモリ管理の課題について) - Go言語のコンパイラ(
cmd/cc
)のソースコード (特にsrc/cmd/cc/pgen.c
の他のバージョンや関連ファイル) - ARMアーキテクチャの関数呼び出し規約やメモリモデルに関する一般的な情報 (ARMビルドが壊れた原因の推測のため)
- Goのコミット履歴やメーリングリストの議論 (関連する問題やその後の解決策について)
[インデックス 16867] ファイルの概要
このコミットは、GoランタイムにおけるC関数呼び出し時のポインタマップ生成に関する変更を取り消すものです。具体的には、以前のコミット (CL 11683043 / bb75d03e6ccb) で導入された、C関数の引数ポインタマップを生成するロジックが、ARMアーキテクチャでのビルドを壊したため、その変更を元に戻しています。
コミット
commit b3defa2e8e66f5a03ce4ac344b9e5bcf3b4321d3
Author: Keith Randall <khr@golang.org>
Date: Wed Jul 24 15:04:10 2013 -0700
undo CL 11683043 / bb75d03e6ccb
Broke arm build.
R=dave
««« original CL description
cc: generate argument pointer maps for C functions.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/11683043
»»»
R=golang-dev
CC=golang-dev
https://golang.org/cl/11788043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b3defa2e8e66f5a03ce4ac344b9e5bcf3b4321d3
元コミット内容
このコミットは、以下の元のコミット(CL 11683043 / bb75d03e6ccb)の変更を取り消すものです。
cc: generate argument pointer maps for C functions.
元のコミットは、Goのコンパイラ(cc
)がC関数を呼び出す際に、その引数に含まれるポインタを追跡するための「ポインタマップ」を生成する機能を追加しようとしたものでした。これは、Goのガベージコレクション(GC)がCコードとの相互運用性を正しく処理するために必要となる情報です。
変更の背景
このコミットの背景には、GoのガベージコレクションとC言語との相互運用性における課題があります。Goは独自のガベージコレクタを持っており、ヒープ上のポインタを正確に識別し、到達可能なオブジェクトをマークすることでメモリを管理します。しかし、GoプログラムがC関数を呼び出す場合、C関数はGoのGCからは見えないメモリ領域(CスタックやCヒープ)にポインタを保持する可能性があります。
元のコミット(CL 11683043)は、この問題を解決するための一歩として、C関数に渡される引数の中にポインタが含まれている場合に、Goのランタイムがそのポインタの位置を把握できるようにするための「ポインタマップ」を生成しようとしました。これにより、GCがC関数呼び出し中にGoのヒープオブジェクトがCコードによって参照されていることを認識し、それらのオブジェクトが誤って回収されるのを防ぐことが期待されました。
しかし、この変更が導入された結果、「Broke arm build.」(ARMビルドを壊した)という問題が発生しました。これは、ARMアーキテクチャ特有のコンパイラやランタイムの挙動、あるいはメモリレイアウトの差異などにより、ポインタマップ生成ロジックが正しく機能しなかったか、予期せぬ副作用を引き起こしたためと考えられます。Goのクロスコンパイルとマルチアーキテクチャサポートは重要な機能であるため、特定のアーキテクチャでのビルドが壊れることは許容されません。そのため、問題の修正に時間がかかることを考慮し、一時的に元の変更を取り消す判断がなされました。
前提知識の解説
ガベージコレクション (GC) とポインタマップ
Go言語は、メモリ管理にガベージコレクション(GC)を採用しています。GCは、プログラムが動的に確保したメモリ領域のうち、もはやどの部分からも参照されていない(到達不可能になった)ものを自動的に解放する仕組みです。GoのGCは、主に「マーク&スイープ」方式をベースとしており、プログラムの実行中にヒープ上のオブジェクトを走査し、到達可能なオブジェクトをマークし、マークされなかったオブジェクトを解放します。
GCが正しく機能するためには、ヒープ上のどの値がポインタであるかを正確に識別する必要があります。なぜなら、ポインタだけが他のオブジェクトへの参照を保持し、GCの走査の起点となるからです。このポインタの識別を助けるのが「ポインタマップ」です。ポインタマップは、特定のメモリ領域(例えば、スタックフレームやヒープオブジェクトの構造体)内のどのオフセットにポインタが存在するかを示すビットマップやリストのようなデータ構造です。Goのランタイムは、このポインタマップを利用して、GCの走査時にポインタを効率的に見つけ出し、追跡します。
CgoとGo/C間の相互運用性
Go言語は、cgo
ツールを通じてC言語のコードと相互運用する機能を提供しています。これにより、Goプログラムから既存のCライブラリを呼び出したり、CコードからGoの関数を呼び出したりすることが可能になります。しかし、GoとCは異なるメモリ管理モデルとランタイムを持っています。
- Goのメモリ管理: GoのGCが管理するヒープと、Goルーチンのスタックがあります。
- Cのメモリ管理:
malloc
/free
による手動管理、およびC関数のスタックがあります。
GoのGCは、Goのヒープとスタック上のポインタは追跡できますが、CのスタックやCのヒープに存在するポインタは直接追跡できません。もしGoのヒープオブジェクトへのポインタがCのスタックやヒープに渡され、Cコードがそのポインタを保持している間にGoのGCが実行されると、GCはそのGoヒープオブジェクトがまだ参照されていることを認識できず、誤って解放してしまう可能性があります(Use-After-Freeバグ)。
この問題を解決するためには、GoのランタイムがC関数呼び出し中にC側のメモリに存在するGoヒープへのポインタを認識し、GCの対象から除外するか、GCの走査に含める必要があります。ポインタマップは、この情報を提供する重要なメカニズムの一つです。
src/cmd/cc/pgen.c
src/cmd/cc/pgen.c
は、Goのツールチェインの一部であるcc
コマンド(Cコンパイラ)のバックエンドに関連するファイルです。このファイルは、GoのコンパイラがCコードを処理し、Goランタイムとの連携に必要な情報を生成する役割を担っています。特に、関数呼び出し規約、スタックフレームのレイアウト、そしてガベージコレクションに関連するメタデータ(ポインタマップなど)の生成に関わっています。
技術的詳細
このコミットは、src/cmd/cc/pgen.c
ファイルから、C関数呼び出し時の引数ポインタマップを生成するためのロジックを削除しています。具体的には、以下の関数とそれに関連するコードが削除されました。
static int32 pointermap(Sym *gcsym, int32 offset);
static int32 pointermap_type(Type *t, int32 offset, int32 baseidx);
これらの関数は、C関数の引数リストを走査し、どのオフセットにポインタが存在するかを示すビットマップを構築することを目的としていました。
pointermap_type
関数
この関数は、与えられた型 t
とオフセット offset
に基づいて、ポインタマップのビットを計算していました。
TIND
(ポインタ型) やTARRAY
(Cでは配列はポインタとして渡される) の場合、指定されたオフセットがポインタのサイズ(ewidth[TIND]
)の倍数であることを確認し、対応するビットを設定していました。TSTRUCT
(構造体) の場合、構造体の各フィールドを再帰的に走査してポインタマップを構築していました。TUNION
(共用体) の場合、共用体のすべてのメンバーが同じポインタマップを持つことを要求していました。これは、共用体内のポインタの配置がメンバーによって異なる場合に、GCが混乱するのを防ぐためと考えられます。
pointermap
関数
この関数は、C関数の引数リスト全体に対してポインタマップを計算し、そのデータをgcsym
(ガベージコレクションシンボル)に格納していました。
hasdotdotdot()
(可変引数関数)の場合、ポインタマップの生成を諦め、nptrs=0
としていました。これは、可変引数関数の引数リストの解析が複雑であるため、初期段階ではサポートを見送ったものと考えられます。- 引数リストのサイズからポインタの数を計算し、
nptrs
として記録していました。 - 引数リストを走査し、
pointermap_type
を呼び出して各引数のポインタマップビットを計算し、それらを結合して最終的なポインタマップを生成していました。 - Cの呼び出し規約で、構造体が隠れた最初の引数(ポインタ)によって返される場合、そのポインタもマップに含めていました。
削除されたコードの影響
これらの関数が削除されたことにより、GoのコンパイラはC関数呼び出し時の引数に関するポインタマップ情報を生成しなくなりました。これは、GoのGCがC関数呼び出し中にGoヒープオブジェクトへの参照を正確に追跡できなくなることを意味します。結果として、CコードがGoヒープオブジェクトへのポインタを保持している間にGCが実行されると、そのオブジェクトが誤って解放されるリスクが生じます。
この変更がARMビルドを壊した具体的な原因はコミットメッセージからは不明ですが、一般的には以下のような理由が考えられます。
- アライメントの問題: ARMアーキテクチャは、特定のデータ型に対して厳密なアライメント要件を持つことがあります。ポインタマップの計算やメモリへの書き込みが、ARMの特定のアライメント要件を満たしていなかった可能性があります。
- ポインタサイズの差異: 32ビットARMと64ビットARMなど、ポインタのサイズが異なる環境での考慮不足があったかもしれません。
- スタックフレームのレイアウト: ARMの関数呼び出し規約やスタックフレームのレイアウトが、ポインタマップ生成ロジックの前提と異なっていた可能性があります。
- コンパイラのバグ:
src/cmd/cc
自体、あるいはGoのツールチェインの他の部分に、ARMアーキテクチャ特有のバグがあり、この新しいポインタマップ生成ロジックと組み合わさることで顕在化した可能性もあります。
このコミットは、機能の追加よりも安定性を優先し、問題のある変更を一時的にロールバックするという、ソフトウェア開発における一般的なプラクティスを示しています。
コアとなるコードの変更箇所
src/cmd/cc/pgen.c
ファイルにおいて、主に以下の変更が行われました。
-
pointermap
およびpointermap_type
関数の削除:static int32 pointermap(Sym *gcsym, int32 offset);
の前方宣言が削除されました。pointermap_type
関数(638行目から700行目)が完全に削除されました。pointermap
関数(703行目から757行目)が完全に削除されました。
-
codgen
関数内のポインタマップ生成ロジックの削除:- 元のコミットで追加された、
AFUNCDATA
命令を使ってGCシンボルにポインタマップデータを関連付ける部分が削除されました。具体的には、以下のコードブロックが削除されました。
/* * generate funcdata symbol for this function. * data is filled in at the end of codgen(). */ snprint(namebuf, sizeof namebuf, "gc·%d", ngcsym++); gcsym = slookup(namebuf); gcsym->class = CSTATIC; memset(&nod, 0, sizeof nod); nod.op = ONAME; nod.sym = gcsym; nod.class = CSTATIC; gins(AFUNCDATA, nodconst(FUNCDATA_GC), &nod);
- 代わりに、以前のコード(ポインタマップ生成ロジックが導入される前のコード)が復元されました。これは、
snprint
、slookup
、memset
、gins
を使ってFUNCDATA_GC
シンボルを生成する部分が、codgen
関数の別の場所(156行目以降)に移動されたことを意味します。
- 元のコミットで追加された、
-
gextern
呼び出しの変更:gextern(gcsym, nodconst(0), off, 4); // nptrs
の行が追加され、pointermap
関数が削除されたため、nptrs
(ポインタの数) を直接0として書き込むようになりました。off = pointermap(gcsym, off); // nptrs and ptrs[...]
の行が削除されました。
これらの変更は、C関数呼び出し時のポインタマップ生成に関連するすべてのコードパスを、src/cmd/cc/pgen.c
から完全に削除することを目的としています。
コアとなるコードの解説
このコミットの核心は、GoのコンパイラがC関数呼び出しの際に生成するメタデータから、ポインタマップに関する情報を削除した点にあります。
src/cmd/cc/pgen.c
は、GoのCコンパイラ(cmd/cc
)のバックエンドの一部であり、CソースコードをGoのオブジェクトファイル形式に変換する際に、Goランタイムが必要とする情報を埋め込む役割を担っています。特に、Goのガベージコレクション(GC)がC関数呼び出し中にGoヒープ上のオブジェクトを正しく追跡できるように、C関数の引数やローカル変数にGoヒープへのポインタが含まれている場合に、その位置を示すメタデータ(ポインタマップ)を生成することが求められます。
元のコミット(CL 11683043)では、codgen
関数内でAFUNCDATA
命令とFUNCDATA_GC
定数を使用して、C関数のポインタマップ情報をGoのオブジェクトファイルに埋め込もうとしていました。FUNCDATA_GC
は、GoランタイムがGC情報を取得するために使用する特別なデータセクションを指します。このデータセクションには、関数のスタックフレーム内のポインタの位置を示すビットマップなどが含まれます。
削除されたpointermap
関数とpointermap_type
関数は、このポインタマップの具体的なビットマップを計算するロジックを実装していました。pointermap_type
は、Cの型システム(構造体、ポインタ、配列など)を走査し、ポインタが存在するオフセットを特定してビットマップに変換します。pointermap
は、関数の引数リスト全体に対してこの処理を適用し、最終的なポインタマップを生成していました。
このコミットでは、これらのポインタマップ生成ロジックが完全に削除されました。これは、ARMビルドが壊れたという問題が、このポインタマップ生成ロジックのバグ、あるいはARMアーキテクチャ特有の制約との不整合に起因すると判断されたためです。ポインタマップの生成を停止することで、一時的にARMビルドの問題を回避し、Goのツールチェインの安定性を確保しました。
結果として、このコミットが適用された状態では、GoのGCはC関数呼び出し中にC側のスタックやメモリに存在するGoヒープへのポインタを正確に追跡できません。これは、Cgoを使用するGoプログラムにおいて、C関数がGoヒープオブジェクトへのポインタを保持している場合に、GCがそのオブジェクトを誤って回収してしまう可能性(Use-After-Free)があることを意味します。この問題は、その後のコミットでより堅牢な方法で解決されることになりますが、このコミットは、GoのGCとCgoの相互運用性における初期の課題と、特定のアーキテクチャでの安定性確保の重要性を示しています。
関連リンク
- 元のコミット (CL 11683043): https://golang.org/cl/11683043 (このコミットで取り消された変更)
- このコミット (CL 11788043): https://golang.org/cl/11788043
参考にした情報源リンク
- Go言語のガベージコレクションに関する公式ドキュメントやブログ記事 (GoのGCの仕組み、ポインタマップの概念について)
- Go言語の
cgo
に関する公式ドキュメント (GoとCの相互運用性、メモリ管理の課題について) - Go言語のコンパイラ(
cmd/cc
)のソースコード (特にsrc/cmd/cc/pgen.c
の他のバージョンや関連ファイル) - ARMアーキテクチャの関数呼び出し規約やメモリモデルに関する一般的な情報 (ARMビルドが壊れた原因の推測のため)
- Goのコミット履歴やメーリングリストの議論 (関連する問題やその後の解決策について)
- Go runtime handles pointers when interacting with C functions: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGbybJqNfHZZch-XoHDAmDwEcbbm0q4TDbSkVka-mUt0aSH6KzzSyUHG1ogrpkp81Qb9P4F5rzBJgXwd2aSeCbWJsXNPuBgiUAHzIJf50MnquoEK2YqzraSexYH0do66ZX3eDU6lKwNuDB49cMa6s_OeFcT90sI-rlxzKIplJzMSp3_4WQ=
- Go's Garbage Collector and Pointer Movement: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHsMaTA0yPuc3IO65kv9d9s3kOHVarJyl37q36p9MMbXjpn4G7krXp6OY4Xiq1ur4TEveHhW5veDoUu232RuLK5pw4tu1aw6L7ZcGfrABchyUlL7v3OAIGeCcIHq-4g5GbNEW2sIJA9pgPWvv13t3qA-expIVIOZiR24PMIaHqTL_a6UiuQd08gPOMcOcWlW4Fw0zzqAXNR9WEalDjwsWT
- Cgo Pointer Passing Rules: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFrVrbONc5rB-7NyMXFxvzWGXRVIbm4fkOtM9mCl6HNVt6eoTqcffuBh7O826clER3-GMluIsDqpS5HR1t8VbLMIHeQh1X6RDH8SAszcHLVPkGj3HuJsVk34U9I_vo4yh_S_6z2
- Workarounds and Best Practices for Pointer Handling: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFi33V5QTqthn1MqEsjHfMQgVJ1rwIJdfcjXZXriflhPpFavmvMygVFTmgMaklYWtGUvSMRszhi_J5uQz0xQynvj7t-ry8WNnaSEVDt51X6tADnDurAa2NSDpwmam8xsbn05qz8ro-OR1m2pvT2OSTRiAm3QGuV478DlV-2VhZZfEPuh_TSuoYMVQs0mPvoZnCNaUx8ShcATT_vczo=
- Memory Management Implications: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEkhsYXFerZXt1d58fCj2v53r6l9h6uxEPI3K_1QDw6pLKRSDK7wiCQWUfMfswCZhfzhq6_oJ-bmmqLmRZ6QoaF0C54PybvJK5vCL0g5-Yg-QtR5kqzHa_e_RsoMFcNiTNqmVDOm4UyQ_5x8GqdpI2Q