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

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

このコミットは、Goランタイム内の複数のC言語で定義されたグローバル変数に対して、ガベージコレクタがポインタを含まないとマークする変更を導入しています。これにより、ガベージコレクションの効率が向上し、特に32ビット環境での誤ったオブジェクトの保持(false retention)を防ぐことを目的としています。

変更されたファイルは以下の通りです。

  • src/pkg/runtime/alg.goc
  • src/pkg/runtime/heapdump.c
  • src/pkg/runtime/malloc.goc
  • src/pkg/runtime/msize.c
  • src/pkg/runtime/os_darwin.c
  • src/pkg/runtime/os_dragonfly.c
  • src/pkg/runtime/os_freebsd.c
  • src/pkg/runtime/os_netbsd.c
  • src/pkg/runtime/os_openbsd.c
  • src/pkg/runtime/os_solaris.c
  • src/pkg/runtime/proc.c

コミット

このコミットは、GoランタイムがC言語で定義された特定のグローバル変数を、ガベージコレクタがポインタを含まないものとして扱うようにマークするものです。これにより、ガベージコレクタがこれらの領域を保守的にスキャンする際のオーバーヘッドを削減し、特に32ビットシステムにおいて、実際には到達不可能であるにもかかわらずポインタと誤認されてオブジェクトが解放されない「誤った保持(false retention)」の問題を回避します。

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

https://github.com/golang/go/commit/548b15def6c22741d1c22fe911ff23c0b224fb88

元コミット内容

runtime: mark some C globals as having no pointers.

C globals are conservatively scanned.  This helps
avoid false retention, especially for 32 bit.

LGTM=rsc
R=golang-codereviews, khr, rsc
CC=golang-codereviews
https://golang.org/cl/102040043

変更の背景

Goのガベージコレクタ(GC)は、プログラムが使用しなくなったメモリを自動的に解放する役割を担っています。Goのオブジェクトは通常、GCによって正確にスキャンされ、ポインタが指す先を追跡することで、到達可能なオブジェクトを特定します。しかし、Goランタイムの一部はC言語で書かれており、C言語のグローバル変数はGoのGCからはその内容がポインタであるか否かを正確に判断することが困難な場合があります。

このようなC言語のグローバル変数に対しては、GoのGCは「保守的スキャン(Conservative Scanning)」と呼ばれる手法を用います。これは、メモリ領域内の値がポインタのように見える場合、それが実際にポインタであるかどうかにかかわらず、ポインタであると仮定してその先を追跡する方式です。この保守的スキャンは安全性を高める一方で、以下のような問題を引き起こす可能性があります。

  1. パフォーマンスの低下: ポインタではないデータ(例えば、単なる整数値)が偶然ポインタのように見える場合、GCはその値をポインタとして追跡しようとします。これにより、不要なメモリ領域までスキャンすることになり、GCの実行時間が長くなる可能性があります。
  2. 誤った保持(False Retention): 最も深刻な問題は、実際にはポインタではない値が偶然有効なメモリアドレスを指しているように見え、その結果、本来解放されるべきオブジェクトが「到達可能」であると誤認されてしまうことです。これは特に32ビットシステムで顕著になります。32ビット環境ではアドレス空間が小さいため、ランダムな整数値が有効なメモリアドレスと一致する確率が高くなります。これにより、メモリリークのような現象が発生し、メモリ使用量が増加する可能性があります。

このコミットは、Goランタイム内の特定のCグローバル変数がポインタを含まないことが既知であるため、それらを明示的にGCに伝えることで、これらの問題を解決しようとしています。

前提知識の解説

Goのガベージコレクション (GC) の基本

Goのガベージコレクタは、並行マーク&スイープ方式を採用しています。これは、プログラムの実行と並行してGCが動作し、アプリケーションの一時停止(STW: Stop-The-World)時間を最小限に抑えることを目指しています。GCは、プログラムが現在使用しているメモリ(到達可能なオブジェクト)を特定し、それ以外のメモリ(到達不可能なオブジェクト)を解放します。

  • マークフェーズ: GCは、ルート(グローバル変数、スタック、レジスタなど)から開始し、ポインタをたどって到達可能なすべてのオブジェクトにマークを付けます。
  • スイープフェーズ: マークされなかったオブジェクトのメモリを解放し、再利用可能な状態にします。

GoのGCは、通常「正確なGC(Precise GC)」です。これは、Goの型情報に基づいて、メモリ内のどの値がポインタであるかを正確に識別できることを意味します。これにより、GCはポインタではないデータを誤って追跡することなく、効率的に動作できます。

Cgo (GoとCの連携)

Goプログラムは、Cgoというメカニズムを通じてC言語のコードを呼び出すことができます。Goランタイム自体も、OSとのインターフェースや低レベルの処理のためにC言語(またはアセンブリ言語)で書かれた部分を含んでいます。Cgoを使用すると、GoのコードからCの関数を呼び出したり、Cのデータ構造を扱ったりすることが可能になります。

しかし、C言語のデータ構造はGoの型システムとは異なるため、Goの正確なGCがCのメモリ領域を直接正確にスキャンすることは困難です。

ポインタスキャンと保守的スキャン (Conservative Scanning)

  • 正確なスキャン(Precise Scanning): Goのオブジェクトに対して行われるスキャンです。Goの型情報に基づいて、メモリ内のどのオフセットにポインタが存在するかを正確に知っています。これにより、ポインタではない値を誤って追跡することはありません。
  • 保守的スキャン(Conservative Scanning): C言語のメモリ領域や、GoのGCが型情報を完全に把握できない領域に対して行われるスキャンです。この方式では、メモリ領域内のすべてのワード(通常はCPUのレジスタ幅、32ビットまたは64ビット)を読み込み、それが有効なポインタのように見える場合(例えば、ヒープ内の有効なオブジェクトを指しているように見える場合)、それをポインタであると仮定して追跡します。

保守的スキャンは、正確な型情報がない場合に安全性を確保するためのフォールバックメカニズムですが、前述の通り「誤った保持」やパフォーマンスの問題を引き起こす可能性があります。

32ビット環境でのメモリ管理の課題

32ビットシステムでは、メモリアドレス空間が2^32バイト(約4GB)に制限されます。この限られたアドレス空間では、ランダムな整数値が偶然有効なメモリアドレスと一致する確率が、64ビットシステム(2^64バイトのアドレス空間)に比べて格段に高くなります。

例えば、あるCグローバル変数に単なる整数値 0x12345678 が格納されていたとします。32ビットシステムでは、この値が偶然、ヒープ上の有効なオブジェクトのアドレス 0x12345678 と一致する可能性があります。GCが保守的スキャンを行うと、この整数値をポインタと誤認し、そのアドレスが指すオブジェクトを「到達可能」と判断してしまい、本来解放されるべきオブジェクトがメモリ上に残り続けてしまいます。これが「誤った保持(False Retention)」です。

#pragma dataflag NOPTR の意味

このコミットで導入されている #pragma dataflag NOPTR は、Goコンパイラ(具体的にはリンカ cmd/ld)に対する指示です。これは、続くデータセクションの変数(グローバル変数や静的変数)がポインタを含まないことを明示的に宣言するために使用されます。

このプラグマが適用された変数領域は、GoのGCによって保守的スキャンの対象から除外されます。GCは、これらの変数の内容をポインタとして解釈しようとせず、単なる非ポインタデータとして扱います。これにより、誤った保持を防ぎ、GCの効率を向上させることができます。

技術的詳細

このコミットの核心は、GoランタイムのC言語部分で定義されている特定のグローバル変数に対して、#pragma dataflag NOPTR を適用することです。これにより、Goのガベージコレクタがこれらの変数をスキャンする際に、ポインタが含まれていないことを保証し、保守的スキャンによる誤ったポインタの検出を防ぎます。

具体的には、以下の種類の変数が対象となっています。

  1. ハッシュ乱数データ: runtime·aeskeyschedurandom_data のような、暗号化や乱数生成に使用されるバイト配列。これらは純粋なデータであり、ポインタを含むことはありません。
  2. メモリ管理関連の内部データ構造: runtime·mheap (Goのヒープ構造体)、mstats (メモリ統計情報)、runtime·class_to_sizeruntime·class_to_allocnpagesruntime·size_to_class8runtime·size_to_class128 (メモリ割り当てのサイズクラス関連のルックアップテーブル) など。これらの構造体や配列は、メモリ管理の内部状態を保持しますが、それ自体がGoのヒープオブジェクトへのポインタを直接保持するわけではありません(あるいは、保持していたとしても、GCがそれらを正確にスキャンできる別のメカニズムがあるか、あるいはそれらがGCの対象外のメモリ領域を指しているため、保守的スキャンが不要であると判断された)。特に runtime·mheap は、Goのヒープのメタデータを管理する重要な構造体ですが、このコミットの時点では、その内部にGCが保守的にスキャンする必要があるようなポインタは含まれていないと判断されています。
  3. プロセッサ記述子: pdesc は、Goのスケジューラが使用するプロセッサ(P)の記述子配列です。これも内部的な状態管理のためのデータであり、ポインタを含まないことが保証されています。
  4. ヒープダンプ関連のバッファ: buf はヒープダンプ処理で使用される一時的なバッファです。これも純粋なバイト配列であり、ポインタを含みません。

これらの変数は、Goランタイムの低レベルな部分で使用されており、その内容がポインタではないことが明確です。#pragma dataflag NOPTR を適用することで、GCはこれらの領域をスキップするか、あるいは非ポインタデータとして効率的に処理できるようになります。これにより、特に32ビット環境で発生しやすかった、偶然のビットパターンがポインタと誤認されることによる「誤った保持」の問題が解消され、メモリ使用量の増加やGCの一時停止時間の延長を防ぐことができます。

この変更は、GoのGCの正確性を高め、ランタイムの安定性とパフォーマンスを向上させるための重要な改善です。

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

このコミットでは、GoランタイムのC言語ソースファイルにおいて、特定のグローバル変数宣言の直前に #pragma dataflag NOPTR が追加されています。

以下に主要な変更箇所を抜粋します。

src/pkg/runtime/alg.goc

--- a/src/pkg/runtime/alg.goc
+++ b/src/pkg/runtime/alg.goc
@@ -465,6 +465,7 @@ runtime·algarray[] =
 // Runtime helpers.
 
 // used in asm_{386,amd64}.s
+#pragma dataflag NOPTR
 byte runtime·aeskeysched[HashRandomBytes];
 
 void

src/pkg/runtime/heapdump.c

--- a/src/pkg/runtime/heapdump.c
+++ b/src/pkg/runtime/heapdump.c
@@ -67,6 +68,7 @@ static uintptr dumpfd;
 enum {
 	BufSize = 4096,
 };
+#pragma dataflag NOPTR
 static byte buf[BufSize];
 static uintptr nbuf;
 

src/pkg/runtime/malloc.goc

--- a/src/pkg/runtime/malloc.goc
+++ b/src/pkg/runtime/malloc.goc
@@ -19,6 +19,7 @@ package runtime
 // Mark mheap as 'no pointers', it does not contain interesting pointers but occupies ~45K.
 #pragma dataflag NOPTR
 MHeap runtime·mheap;
+#pragma dataflag NOPTR
 MStats mstats;
 
 int32	runtime·checking;

src/pkg/runtime/msize.c

--- a/src/pkg/runtime/msize.c
+++ b/src/pkg/runtime/msize.c
@@ -28,8 +28,11 @@
 #include "runtime.h"
 #include "arch_GOARCH.h"
 #include "malloc.h"
+#include "../../cmd/ld/textflag.h"
 
+#pragma dataflag NOPTR
 int32 runtime·class_to_size[NumSizeClasses];
+#pragma dataflag NOPTR
 int32 runtime·class_to_allocnpages[NumSizeClasses];
 
 // The SizeToClass lookup is implemented using two arrays,
@@ -41,7 +44,9 @@ int32 runtime·class_to_allocnpages[NumSizeClasses];
 // size divided by 128 (rounded up).  The arrays are filled in
 // by InitSizes.
 
+#pragma dataflag NOPTR
 int8 runtime·size_to_class8[1024/8 + 1];
+#pragma dataflag NOPTR
 int8 runtime·size_to_class128[(MaxSmallSize-1024)/128 + 1];
 
 void runtime·testdefersizes(void);

src/pkg/runtime/os_darwin.c および他のOS固有のファイル (os_dragonfly.c, os_freebsd.c, os_netbsd.c, os_openbsd.c, os_solaris.c)

--- a/src/pkg/runtime/os_darwin.c
+++ b/src/pkg/runtime/os_darwin.c
@@ -59,6 +59,7 @@ runtime·osinit(void)
 void
 runtime·get_random_data(byte **rnd, int32 *rnd_len)
 {
+\t#pragma dataflag NOPTR
 \tstatic byte urandom_data[HashRandomBytes];
 \tint32 fd;\
 \tfd = runtime·open(\"/dev/urandom\", 0 /* O_RDONLY */, 0);\

src/pkg/runtime/proc.c

--- a/src/pkg/runtime/proc.c
+++ b/src/pkg/runtime/proc.c
@@ -2597,6 +2597,7 @@ struct Pdesc
 	uint32	syscalltick;
 	int64	syscallwhen;
 };
+#pragma dataflag NOPTR
 static Pdesc pdesc[MaxGomaxprocs];
 
 static uint32

コアとなるコードの解説

追加された #pragma dataflag NOPTR は、Goのリンカ(cmd/ld)に対する指示であり、その後に続くグローバル変数や静的変数がポインタを含まないことを示します。これにより、Goのガベージコレクタはこれらのメモリ領域をスキャンする際に、ポインタの有無をチェックする必要がなくなります。

各変更箇所とその変数の役割は以下の通りです。

  • byte runtime·aeskeysched[HashRandomBytes]; (src/pkg/runtime/alg.goc)
    • runtime·aeskeysched は、AES暗号化アルゴリズムの鍵スケジュールに使用されるバイト配列です。これは純粋なデータであり、Goのヒープオブジェクトへのポインタを含むことはありません。
  • static byte buf[BufSize]; (src/pkg/runtime/heapdump.c)
    • buf は、ヒープダンプ(メモリの内容をファイルに書き出す処理)を行う際に使用される一時的なバッファです。これも純粋なバイトデータであり、ポインタを含みません。
  • MHeap runtime·mheap; (src/pkg/runtime/malloc.goc)
    • runtime·mheap は、Goのメモリヒープ全体を管理する主要な構造体です。このコミットの時点では、MHeap 構造体自体がGCが保守的にスキャンする必要があるようなGoのヒープオブジェクトへのポインタを直接保持していないと判断されています。これは、MHeap が主にメモリブロックの管理情報(アリーナ、スパンなど)を保持しており、それらの情報はGCが別途正確に追跡できるか、あるいはポインタではないデータとして扱われるためです。
  • MStats mstats; (src/pkg/runtime/malloc.goc)
    • mstats は、Goのメモリ統計情報(ヒープの使用量、GCの回数など)を保持する構造体です。これも純粋な統計データであり、ポインタを含みません。
  • int32 runtime·class_to_size[NumSizeClasses]; (src/pkg/runtime/msize.c)
  • int32 runtime·class_to_allocnpages[NumSizeClasses]; (src/pkg/runtime/msize.c)
  • int8 runtime·size_to_class8[1024/8 + 1]; (src/pkg/runtime/msize.c)
  • int8 runtime·size_to_class128[(MaxSmallSize-1024)/128 + 1]; (src/pkg/runtime/msize.c)
    • これらは、Goのメモリ割り当て器が、要求されたサイズに基づいて適切なメモリクラス(スパンサイズ)を決定するために使用するルックアップテーブルです。これらはすべて整数値の配列であり、ポインタを含みません。
  • static byte urandom_data[HashRandomBytes]; (src/pkg/runtime/os_darwin.c など、各OS固有のファイル)
    • urandom_data は、OSから乱数データを読み込むための一時的なバッファです。これも純粋なバイトデータであり、ポインタを含みません。
  • static Pdesc pdesc[MaxGomaxprocs]; (src/pkg/runtime/proc.c)
    • pdesc は、Goのスケジューラが管理する各プロセッサ(P)の記述子(Pdesc)の配列です。Pdesc 構造体は、Goルーチン(goroutine)の実行コンテキストや統計情報などを保持しますが、このコミットの時点では、GCが保守的にスキャンする必要があるようなGoのヒープオブジェクトへのポインタを直接保持していないと判断されています。

これらの変数は、Goランタイムの内部動作に不可欠なデータですが、その性質上、Goのヒープオブジェクトへのポインタを保持することはありません。したがって、#pragma dataflag NOPTR を適用することで、GCがこれらの領域を不必要にスキャンするのを防ぎ、特に32ビット環境での「誤った保持」の問題を効果的に回避し、GCの効率と正確性を向上させています。

関連リンク

  • Go CL 102040043: https://golang.org/cl/102040043
  • Goのガベージコレクションに関する公式ドキュメント (Go 1.5以降のGCについて): https://go.dev/doc/gc-guide (このコミットはGo 1.3以前の時期のものですが、GCの基本的な概念は共通しています)

参考にした情報源リンク

I will now output the generated Markdown to standard output.

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

このコミットは、Goランタイム内の複数のC言語で定義されたグローバル変数に対して、ガベージコレクタがポインタを含まないとマークする変更を導入しています。これにより、ガベージコレクションの効率が向上し、特に32ビット環境での誤ったオブジェクトの保持(false retention)を防ぐことを目的としています。

変更されたファイルは以下の通りです。

*   `src/pkg/runtime/alg.goc`
*   `src/pkg/runtime/heapdump.c`
*   `src/pkg/runtime/malloc.goc`
*   `src/pkg/runtime/msize.c`
*   `src/pkg/runtime/os_darwin.c`
*   `src/pkg/runtime/os_dragonfly.c`
*   `src/pkg/runtime/os_freebsd.c`
*   `src/pkg/runtime/os_netbsd.c`
*   `src/pkg/runtime/os_openbsd.c`
*   `src/pkg/runtime/os_solaris.c`
*   `src/pkg/runtime/proc.c`

## コミット

このコミットは、GoランタイムがC言語で定義された特定のグローバル変数を、ガベージコレクタがポインタを含まないものとして扱うようにマークするものです。これにより、ガベージコレクタがこれらの領域を保守的にスキャンする際のオーバーヘッドを削減し、特に32ビットシステムにおいて、実際には到達不可能であるにもかかわらずポインタと誤認されてオブジェクトが解放されない「誤った保持(false retention)」の問題を回避します。

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

[https://github.com/golang/go/commit/548b15def6c22741d1c22fe911ff23c0b224fb88](https://github.com/golang/go/commit/548b15def6c22741d1c22fe911ff23c0b224fb88)

## 元コミット内容

runtime: mark some C globals as having no pointers.

C globals are conservatively scanned. This helps avoid false retention, especially for 32 bit.

LGTM=rsc R=golang-codereviews, khr, rsc CC=golang-codereviews https://golang.org/cl/102040043


## 変更の背景

Goのガベージコレクタ(GC)は、プログラムが使用しなくなったメモリを自動的に解放する役割を担っています。Goのオブジェクトは通常、GCによって正確にスキャンされ、ポインタが指す先を追跡することで、到達可能なオブジェクトを特定します。しかし、Goランタイムの一部はC言語で書かれており、C言語のグローバル変数はGoのGCからはその内容がポインタであるか否かを正確に判断することが困難な場合があります。

このようなC言語のグローバル変数に対しては、GoのGCは「保守的スキャン(Conservative Scanning)」と呼ばれる手法を用います。これは、メモリ領域内の値がポインタのように見える場合、それが実際にポインタであるかどうかにかかわらず、ポインタであると仮定してその先を追跡する方式です。この保守的スキャンは安全性を高める一方で、以下のような問題を引き起こす可能性があります。

1.  **パフォーマンスの低下**: ポインタではないデータ(例えば、単なる整数値)が偶然ポインタのように見える場合、GCはその値をポインタとして追跡しようとします。これにより、不要なメモリ領域までスキャンすることになり、GCの実行時間が長くなる可能性があります。
2.  **誤った保持(False Retention)**: 最も深刻な問題は、実際にはポインタではない値が偶然有効なメモリアドレスを指しているように見え、その結果、本来解放されるべきオブジェクトが「到達可能」であると誤認されてしまうことです。これは特に32ビットシステムで顕著になります。32ビット環境ではアドレス空間が小さいため、ランダムな整数値が有効なメモリアドレスと一致する確率が高くなります。これにより、メモリリークのような現象が発生し、メモリ使用量が増加する可能性があります。

このコミットは、Goランタイム内の特定のCグローバル変数がポインタを含まないことが既知であるため、それらを明示的にGCに伝えることで、これらの問題を解決しようとしています。

## 前提知識の解説

### Goのガベージコレクション (GC) の基本

Goのガベージコレクタは、並行マーク&スイープ方式を採用しています。これは、プログラムの実行と並行してGCが動作し、アプリケーションの一時停止(STW: Stop-The-World)時間を最小限に抑えることを目指しています。GCは、プログラムが現在使用しているメモリ(到達可能なオブジェクト)を特定し、それ以外のメモリ(到達不可能なオブジェクト)を解放します。

*   **マークフェーズ**: GCは、ルート(グローバル変数、スタック、レジスタなど)から開始し、ポインタをたどって到達可能なすべてのオブジェクトにマークを付けます。
*   **スイープフェーズ**: マークされなかったオブジェクトのメモリを解放し、再利用可能な状態にします。

GoのGCは、通常「正確なGC(Precise GC)」です。これは、Goの型情報に基づいて、メモリ内のどの値がポインタであるかを正確に識別できることを意味します。これにより、GCはポインタではないデータを誤って追跡することなく、効率的に動作できます。

### Cgo (GoとCの連携)

Goプログラムは、Cgoというメカニズムを通じてC言語のコードを呼び出すことができます。Goランタイム自体も、OSとのインターフェースや低レベルの処理のためにC言語(またはアセンブリ言語)で書かれた部分を含んでいます。Cgoを使用すると、GoのコードからCの関数を呼び出したり、Cのデータ構造を扱ったりすることが可能になります。

しかし、C言語のデータ構造はGoの型システムとは異なるため、Goの正確なGCがCのメモリ領域を直接正確にスキャンすることは困難です。

### ポインタスキャンと保守的スキャン (Conservative Scanning)

*   **正確なスキャン(Precise Scanning)**: Goのオブジェクトに対して行われるスキャンです。Goの型情報に基づいて、メモリ内のどのオフセットにポインタが存在するかを正確に知っています。これにより、ポインタではない値を誤って追跡することはありません。
*   **保守的スキャン(Conservative Scanning)**: C言語のメモリ領域や、GoのGCが型情報を完全に把握できない領域に対して行われるスキャンです。この方式では、メモリ領域内のすべてのワード(通常はCPUのレジスタ幅、32ビットまたは64ビット)を読み込み、それが有効なポインタのように見える場合(例えば、ヒープ内の有効なオブジェクトを指しているように見える場合)、それをポインタであると仮定して追跡します。

保守的スキャンは、正確な型情報がない場合に安全性を確保するためのフォールバックメカニズムですが、前述の通り「誤った保持」やパフォーマンスの問題を引き起こす可能性があります。

### 32ビット環境でのメモリ管理の課題

32ビットシステムでは、メモリアドレス空間が2^32バイト(約4GB)に制限されます。この限られたアドレス空間では、ランダムな整数値が偶然有効なメモリアドレスと一致する確率が、64ビットシステム(2^64バイトのアドレス空間)に比べて格段に高くなります。

例えば、あるCグローバル変数に単なる整数値 `0x12345678` が格納されていたとします。32ビットシステムでは、この値が偶然、ヒープ上の有効なオブジェクトのアドレス `0x12345678` と一致する可能性があります。GCが保守的スキャンを行うと、この整数値をポインタと誤認し、そのアドレスが指すオブジェクトを「到達可能」と判断してしまい、本来解放されるべきオブジェクトがメモリ上に残り続けてしまいます。これが「誤った保持(False Retention)」です。

### `#pragma dataflag NOPTR` の意味

このコミットで導入されている `#pragma dataflag NOPTR` は、Goコンパイラ(具体的にはリンカ `cmd/ld`)に対する指示です。これは、続くデータセクションの変数(グローバル変数や静的変数)がポインタを含まないことを明示的に宣言するために使用されます。

このプラグマが適用された変数領域は、GoのGCによって保守的スキャンの対象から除外されます。GCは、これらの変数の内容をポインタとして解釈しようとせず、単なる非ポインタデータとして扱います。これにより、誤った保持を防ぎ、GCの効率を向上させることができます。

## 技術的詳細

このコミットの核心は、GoランタイムのC言語部分で定義されている特定のグローバル変数に対して、`#pragma dataflag NOPTR` を適用することです。これにより、Goのガベージコレクタがこれらの変数をスキャンする際に、ポインタが含まれていないことを保証し、保守的スキャンによる誤ったポインタの検出を防ぎます。

具体的には、以下の種類の変数が対象となっています。

1.  **ハッシュ乱数データ**: `runtime·aeskeysched` や `urandom_data` のような、暗号化や乱数生成に使用されるバイト配列。これらは純粋なデータであり、ポインタを含むことはありません。
2.  **メモリ管理関連の内部データ構造**: `runtime·mheap` (Goのヒープ構造体)、`mstats` (メモリ統計情報)、`runtime·class_to_size`、`runtime·class_to_allocnpages`、`runtime·size_to_class8`、`runtime·size_to_class128` (メモリ割り当てのサイズクラス関連のルックアップテーブル) など。これらの構造体や配列は、メモリ管理の内部状態を保持しますが、それ自体がGoのヒープオブジェクトへのポインタを直接保持するわけではありません(あるいは、保持していたとしても、GCがそれらを正確にスキャンできる別のメカニズムがあるか、あるいはそれらがGCの対象外のメモリ領域を指しているため、保守的スキャンが不要であると判断された)。特に `runtime·mheap` は、Goのヒープのメタデータを管理する重要な構造体ですが、このコミットの時点では、その内部にGCが保守的にスキャンする必要があるようなポインタは含まれていないと判断されています。
3.  **プロセッサ記述子**: `pdesc` は、Goのスケジューラが使用するプロセッサ(P)の記述子配列です。これも内部的な状態管理のためのデータであり、ポインタを含まないことが保証されています。
4.  **ヒープダンプ関連のバッファ**: `buf` はヒープダンプ処理で使用される一時的なバッファです。これも純粋なバイト配列であり、ポインタを含みません。

これらの変数は、Goランタイムの低レベルな部分で使用されており、その内容がポインタではないことが明確です。`#pragma dataflag NOPTR` を適用することで、GCはこれらの領域をスキップするか、あるいは非ポインタデータとして効率的に処理できるようになります。これにより、特に32ビット環境で発生しやすかった、偶然のビットパターンがポインタと誤認されることによる「誤った保持」の問題が解消され、メモリ使用量の増加やGCの一時停止時間の延長を防ぐことができます。

この変更は、GoのGCの正確性を高め、ランタイムの安定性とパフォーマンスを向上させるための重要な改善です。

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

このコミットでは、GoランタイムのC言語ソースファイルにおいて、特定のグローバル変数宣言の直前に `#pragma dataflag NOPTR` が追加されています。

以下に主要な変更箇所を抜粋します。

**`src/pkg/runtime/alg.goc`**
```diff
--- a/src/pkg/runtime/alg.goc
+++ b/src/pkg/runtime/alg.goc
@@ -465,6 +465,7 @@ runtime·algarray[] =
 // Runtime helpers.
 
 // used in asm_{386,amd64}.s
+#pragma dataflag NOPTR
 byte runtime·aeskeysched[HashRandomBytes];
 
 void

src/pkg/runtime/heapdump.c

--- a/src/pkg/runtime/heapdump.c
+++ b/src/pkg/runtime/heapdump.c
@@ -67,6 +68,7 @@ static uintptr dumpfd;
 enum {
 	BufSize = 4096,
 };
+#pragma dataflag NOPTR
 static byte buf[BufSize];
 static uintptr nbuf;
 

src/pkg/runtime/malloc.goc

--- a/src/pkg/runtime/malloc.goc
+++ b/src/pkg/runtime/malloc.goc
@@ -19,6 +19,7 @@ package runtime
 // Mark mheap as 'no pointers', it does not contain interesting pointers but occupies ~45K.
 #pragma dataflag NOPTR
 MHeap runtime·mheap;
+#pragma dataflag NOPTR
 MStats mstats;
 
 int32	runtime·checking;

src/pkg/runtime/msize.c

--- a/src/pkg/runtime/msize.c
+++ b/src/pkg/runtime/msize.c
@@ -28,8 +28,11 @@
 #include "runtime.h"
 #include "arch_GOARCH.h"
 #include "malloc.h"
+#include "../../cmd/ld/textflag.h"
 
+#pragma dataflag NOPTR
 int32 runtime·class_to_size[NumSizeClasses];
+#pragma dataflag NOPTR
 int32 runtime·class_to_allocnpages[NumSizeClasses];
 
 // The SizeToClass lookup is implemented using two arrays,
@@ -41,7 +44,9 @@ int32 runtime·class_to_allocnpages[NumSizeClasses];
 // size divided by 128 (rounded up).  The arrays are filled in
 // by InitSizes.
 
+#pragma dataflag NOPTR
 int8 runtime·size_to_class8[1024/8 + 1];
+#pragma dataflag NOPTR
 int8 runtime·size_to_class128[(MaxSmallSize-1024)/128 + 1];
 
 void runtime·testdefersizes(void);\

src/pkg/runtime/os_darwin.c および他のOS固有のファイル (os_dragonfly.c, os_freebsd.c, os_netbsd.c, os_openbsd.c, os_solaris.c)

--- a/src/pkg/runtime/os_darwin.c
+++ b/src/pkg/runtime/os_darwin.c
@@ -59,6 +59,7 @@ runtime·osinit(void)
 void
 runtime·get_random_data(byte **rnd, int32 *rnd_len)
 {
+\t#pragma dataflag NOPTR
 \tstatic byte urandom_data[HashRandomBytes];
 \tint32 fd;\
 \tfd = runtime·open(\"/dev/urandom\", 0 /* O_RDONLY */, 0);\

src/pkg/runtime/proc.c

--- a/src/pkg/runtime/proc.c
+++ b/src/pkg/runtime/proc.c
@@ -2597,6 +2597,7 @@ struct Pdesc
 	uint32	syscalltick;
 	int64	syscallwhen;
 };
+#pragma dataflag NOPTR
 static Pdesc pdesc[MaxGomaxprocs];
 
 static uint32

コアとなるコードの解説

追加された #pragma dataflag NOPTR は、Goのリンカ(cmd/ld)に対する指示であり、その後に続くグローバル変数や静的変数がポインタを含まないことを示します。これにより、Goのガベージコレクタはこれらのメモリ領域をスキャンする際に、ポインタの有無をチェックする必要がなくなります。

各変更箇所とその変数の役割は以下の通りです。

  • byte runtime·aeskeysched[HashRandomBytes]; (src/pkg/runtime/alg.goc)
    • runtime·aeskeysched は、AES暗号化アルゴリズムの鍵スケジュールに使用されるバイト配列です。これは純粋なデータであり、Goのヒープオブジェクトへのポインタを含むことはありません。
  • static byte buf[BufSize]; (src/pkg/runtime/heapdump.c)
    • buf は、ヒープダンプ(メモリの内容をファイルに書き出す処理)を行う際に使用される一時的なバッファです。これも純粋なバイトデータであり、ポインタを含みません。
  • MHeap runtime·mheap; (src/pkg/runtime/malloc.goc)
    • runtime·mheap は、Goのメモリヒープ全体を管理する主要な構造体です。このコミットの時点では、MHeap 構造体自体がGCが保守的にスキャンする必要があるようなGoのヒープオブジェクトへのポインタを直接保持していないと判断されています。これは、MHeap が主にメモリブロックの管理情報(アリーナ、スパンなど)を保持しており、それらの情報はGCが別途正確に追跡できるか、あるいはポインタではないデータとして扱われるためです。
  • MStats mstats; (src/pkg/runtime/malloc.goc)
    • mstats は、Goのメモリ統計情報(ヒープの使用量、GCの回数など)を保持する構造体です。これも純粋な統計データであり、ポインタを含みません。
  • int32 runtime·class_to_size[NumSizeClasses]; (src/pkg/runtime/msize.c)
  • int32 runtime·class_to_allocnpages[NumSizeClasses]; (src/pkg/runtime/msize.c)
  • int8 runtime·size_to_class8[1024/8 + 1]; (src/pkg/runtime/msize.c)
  • int8 runtime·size_to_class128[(MaxSmallSize-1024)/128 + 1]; (src/pkg/runtime/msize.c)
    • これらは、Goのメモリ割り当て器が、要求されたサイズに基づいて適切なメモリクラス(スパンサイズ)を決定するために使用するルックアップテーブルです。これらはすべて整数値の配列であり、ポインタを含みません。
  • static byte urandom_data[HashRandomBytes]; (src/pkg/runtime/os_darwin.c など、各OS固有のファイル)
    • urandom_data は、OSから乱数データを読み込むための一時的なバッファです。これも純粋なバイトデータであり、ポインタを含みません。
  • static Pdesc pdesc[MaxGomaxprocs]; (src/pkg/runtime/proc.c)
    • pdesc は、Goのスケジューラが管理する各プロセッサ(P)の記述子(Pdesc)の配列です。Pdesc 構造体は、Goルーチン(goroutine)の実行コンテキストや統計情報などを保持しますが、このコミットの時点では、GCが保守的にスキャンする必要があるようなGoのヒープオブジェクトへのポインタを直接保持していないと判断されています。

これらの変数は、Goランタイムの内部動作に不可欠なデータですが、その性質上、Goのヒープオブジェクトへのポインタを保持することはありません。したがって、#pragma dataflag NOPTR を適用することで、GCがこれらの領域を不必要にスキャンするのを防ぎ、特に32ビット環境での「誤った保持」の問題を効果的に回避し、GCの効率と正確性を向上させています。

関連リンク

  • Go CL 102040043: https://golang.org/cl/102040043
  • Goのガベージコレクションに関する公式ドキュメント (Go 1.5以降のGCについて): https://go.dev/doc/gc-guide (このコミットはGo 1.3以前の時期のものですが、GCの基本的な概念は共通しています)

参考にした情報源リンク