[インデックス 16414] ファイルの概要
このコミットは、Go言語のランタイムにおいて、型付き配列の割り当てを簡素化するために cnewarray() という新しい関数を導入するものです。これにより、reflect パッケージやスライス作成時のメモリ割り当てロジックが整理され、重複が排除されます。
コミット
commit 5782f4117dcb4c8fc40f8110a57ac531a2abdb99
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Mon May 27 11:29:11 2013 +0400
runtime: introduce cnewarray() to simplify allocation of typed arrays
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/9648044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5782f4117dcb4c8fc40f8110a57ac531a2abdb99
元コミット内容
runtime: introduce cnewarray() to simplify allocation of typed arrays
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/9648044
変更の背景
Go言語のランタイムには、様々な場所でメモリ割り当てが行われます。特に、reflect パッケージを通じて動的に型付きのオブジェクトや配列を生成する場合、そしてスライスを作成する場合に、同様のメモリ割り当てロジックが複数箇所に散在していました。これらのロジックは、ゼロサイズの割り当て、ポインタを含まない型(KindNoPointers)の扱い、そしてガベージコレクション(GC)のための型情報の付与など、共通のパターンを持っていました。
この重複はコードの保守性を低下させ、将来的な変更や最適化を困難にする可能性がありました。例えば、メモリ割り当ての挙動を変更する際に、複数の場所を修正する必要が生じ、バグを導入するリスクが高まります。
このコミットの目的は、これらの重複する型付き配列の割り当てロジックを cnewarray() という単一の共通関数に集約し、コードの簡素化と保守性の向上を図ることにあります。これにより、ランタイムのコードベースがよりDRY (Don't Repeat Yourself) になり、一貫性が保たれるようになります。
前提知識の解説
このコミットを理解するためには、以下のGo言語のランタイムに関する基本的な知識が必要です。
1. Goランタイムのメモリ管理
Goランタイムは独自のメモリマネージャとガベージコレクタ(GC)を持っています。プログラムがメモリを要求すると、ランタイムはヒープからメモリを割り当てます。
runtime·mallocgc: Goランタイムにおける主要なメモリ割り当て関数です。サイズ、フラグ(ポインタの有無など)、GC関連の引数を受け取ります。runtime·zerobase: ゼロサイズの割り当て(例えば、長さ0のスライスや配列)に対して返される特別なポインタです。実際のメモリは割り当てられず、すべてのゼロサイズのオブジェクトはこの同じポインタを共有します。KindNoPointers: Goの型情報(Type構造体)に含まれるフラグの一つで、その型がポインタを含まないことを示します。ポインタを含まないオブジェクトはGCの対象外となるため、特別な扱いが可能です。UseSpanType: Goの内部的なGC最適化に関連するフラグです。これが有効な場合、割り当てられたメモリ領域(スパン)に型情報を付与することで、GCが効率的にポインタをスキャンできるようになります。runtime·settype: 割り当てられたメモリブロックに型情報を関連付けるランタイム関数です。GCが正確にポインタを識別し、マーク&スイープ処理を行うために重要です。TypeInfo_SingleObjectは単一のオブジェクト、TypeInfo_Arrayは配列であることを示します。
2. reflect パッケージ
reflect パッケージは、Goプログラムが実行時に自身の構造を検査し、操作するための機能を提供します。
reflect.New(Type): 指定された型の新しいゼロ値を割り当て、その値へのポインタを返します。これは単一のオブジェクトの割り当てに対応します。reflect.NewArray(Type, int): 指定された要素型と長さを持つ新しい配列を割り当て、その配列へのポインタを返します。これは型付き配列の割り当てに対応します。reflect.MakeSlice(Type, int, int): 指定された要素型、長さ、容量を持つ新しいスライスを作成します。スライスは内部的に配列へのポインタ、長さ、容量を持つ構造体です。
これらのreflectパッケージの関数は、最終的にGoランタイムの内部関数を呼び出してメモリを割り当てます。
3. スライス (Slice)
Goのスライスは、配列のセグメントを参照するデータ構造です。スライス自体は小さな構造体ですが、その実体となるデータはヒープ上の配列に格納されます。スライスを作成する際には、その実体となる配列のメモリ割り当てが必要です。
4. intgo 型
intgo はGoランタイム内部で使用される整数型で、Go言語の int 型に対応します。これはプラットフォームのポインタサイズに依存する可能性があり、通常は int32 または int64 になります。
5. MaxMem
Goランタイムが扱える最大メモリサイズを示す定数です。メモリ割り当ての際に、要求されたサイズがこの上限を超えないかチェックするために使用されます。
技術的詳細
このコミットの核心は、src/pkg/runtime/malloc.goc に static void* cnew(Type *typ, intgo n, int32 objtyp) という新しい内部ヘルパー関数を導入し、それを基に runtime·cnew と runtime·cnewarray を実装した点です。
cnew ヘルパー関数の導入
cnew 関数は、単一のオブジェクト (n=1) または配列 (n > 1) の割り当てを共通で処理するための汎用的な関数です。
static void*
cnew(Type *typ, intgo n, int32 objtyp)
{
uint32 flag;
void *ret;
// objtyp のバリデーション
if((objtyp&(PtrSize-1)) != objtyp)
runtime·throw("runtime: invalid objtyp");
// 割り当てサイズが範囲外でないかチェック
if(n < 0 || (typ->size > 0 && n > MaxMem/typ->size))
runtime·panicstring("runtime: allocation size out of range");
// ゼロサイズまたはゼロ長の割り当ての場合
if(typ->size == 0 || n == 0) {
return &runtime·zerobase; // 特殊なゼロベースポインタを返す
}
// ポインタを含まない型の場合、FlagNoPointersを設定
flag = typ->kind&KindNoPointers ? FlagNoPointers : 0;
// 実際のメモリ割り当て
ret = runtime·mallocgc(typ->size*n, flag, 1, 1);
// UseSpanTypeが有効でポインタを含む型の場合、型情報を設定
if(UseSpanType && !flag) {
if(false) // デバッグ用のprintfは常にfalse
runtime·printf("cnew [%D]%S: %p\\n", (int64)n, *typ->string, ret);
runtime·settype(ret, (uintptr)typ | objtyp); // objtyp (TypeInfo_SingleObject or TypeInfo_Array) を付与
}
return ret;
}
この cnew 関数は以下の処理を共通化しています。
objtypのバリデーション:TypeInfo_SingleObjectまたはTypeInfo_Arrayのような型情報フラグが正しい形式であることを確認します。- サイズオーバーフローチェック: 要求された割り当てサイズ (
typ->size * n) がMaxMemを超えないか、または負の値でないかをチェックし、問題があればパニックを発生させます。これは、以前はreflect.MakeSliceなどで個別にチェックされていたオーバーフロー検出を共通化するものです。 - ゼロサイズ割り当ての処理:
typ->sizeが0の場合(例えば、空の構造体やインターフェース)やnが0の場合(長さ0の配列やスライス)、runtime·zerobaseを返します。これにより、不要なメモリ割り当てを避け、すべてのゼロサイズオブジェクトが同じアドレスを共有するようになります。 FlagNoPointersの設定: 割り当てる型がポインタを含まない場合 (typ->kind&KindNoPointers)、FlagNoPointersフラグを設定してruntime·mallocgcに渡します。これにより、GCがこのメモリ領域をスキャンする必要がなくなります。runtime·mallocgcによるメモリ割り当て: 実際のメモリ割り当てはruntime·mallocgc(typ->size*n, flag, 1, 1)を通じて行われます。ここでtyp->size*nは割り当てるバイト数です。- 型情報の付与:
UseSpanTypeが有効で、かつポインタを含む型の場合、runtime·settypeを呼び出して割り当てられたメモリブロックに型情報 ((uintptr)typ | objtyp) を付与します。これにより、GCが正確にポインタを識別し、マーク&スイープ処理を行うことができます。objtyp引数によって、単一オブジェクトか配列かを示すフラグが渡されます。
runtime·cnew と runtime·cnewarray の再実装
cnew ヘルパー関数を基に、既存の runtime·cnew と新しい runtime·cnewarray が簡潔に再実装されました。
runtime·cnew(Type *typ): 単一のオブジェクトを割り当てるためのC言語から呼び出し可能な関数です。内部でcnew(typ, 1, TypeInfo_SingleObject)を呼び出します。runtime·cnewarray(Type *typ, intgo n): 型付き配列を割り当てるためのC言語から呼び出し可能な新しい関数です。内部でcnew(typ, n, TypeInfo_Array)を呼び出します。
変更の影響
src/pkg/reflect/all_test.go:TestSliceOverflowという新しいテストが追加されました。これは、MakeSliceがサイズオーバーフロー時にパニックを起こすことを確認するためのものです。このテストは、cnew関数に導入されたサイズチェックの重要性を示しています。src/pkg/runtime/iface.c:reflect·unsafe_Newとreflect·unsafe_NewArrayの実装が大幅に簡素化されました。reflect·unsafe_Newはruntime·cnew(t)を呼び出すだけになりました。reflect·unsafe_NewArrayはruntime·cnewarray(t, n)を呼び出すだけになりました。 以前はこれらの関数内で直接runtime·mallocgcを呼び出し、ゼロサイズチェック、ポインタフラグ設定、型情報設定などのロジックを個別に持っていました。
src/pkg/runtime/malloc.goc:runtime·cnewの実装がcnewヘルパー関数を使用するように変更され、runtime·cnewarrayが追加されました。また、runtime·new内のデバッグ用printfの括弧が削除され、より簡潔になりました。src/pkg/runtime/malloc.h:runtime·cnewarrayのプロトタイプ宣言が追加されました。src/pkg/runtime/slice.c:makeslice1関数(スライス作成の内部ヘルパー)の実装が簡素化されました。以前はruntime·mallocgcを直接呼び出し、ゼロサイズチェック、ポインタフラグ設定、型情報設定などのロジックを個別に持っていました。これがret->array = runtime·cnewarray(t->elem, cap);という単一の呼び出しに置き換えられました。
コアとなるコードの変更箇所
src/pkg/runtime/iface.c
reflect パッケージからの呼び出しを処理する部分。runtime·cnew と runtime·cnewarray を使用するように変更。
--- a/src/pkg/runtime/iface.c
+++ b/src/pkg/runtime/iface.c
@@ -687,42 +687,14 @@ reflect·unsafe_Typeof(Eface e, Eface ret)
void
reflect·unsafe_New(Type *t, void *ret)
{
- uint32 flag;
-
- flag = t->kind&KindNoPointers ? FlagNoPointers : 0;
- ret = runtime·mallocgc(t->size, flag, 1, 1);
-
- if(UseSpanType && !flag) {
- if(false) {
- runtime·printf("unsafe_New %S: %p\\n", *t->string, ret);
- }
- runtime·settype(ret, (uintptr)t | TypeInfo_SingleObject);
- }
-
+ ret = runtime·cnew(t);
FLUSH(&ret);
}
void
reflect·unsafe_NewArray(Type *t, intgo n, void *ret)
{
- uint64 size;
-
- size = n*t->size;
- if(size == 0)
- ret = (byte*)&runtime·zerobase;
- else if(t->kind&KindNoPointers)
- ret = runtime·mallocgc(size, FlagNoPointers, 1, 1);
- else {
- ret = runtime·mallocgc(size, 0, 1, 1);
-
- if(UseSpanType) {
- if(false) {
- runtime·printf("unsafe_NewArray [%D]%S: %p\\n\", (int64)n, *t->string, ret);
- }
- runtime·settype(ret, (uintptr)t | TypeInfo_Array);
- }
- }
-
+ ret = runtime·cnewarray(t, n);
FLUSH(&ret);
}
src/pkg/runtime/malloc.goc
cnew ヘルパー関数の導入と、runtime·cnew および runtime·cnewarray の実装。
--- a/src/pkg/runtime/malloc.goc
+++ b/src/pkg/runtime/malloc.goc
@@ -729,9 +729,8 @@ runtime·new(Type *typ, uint8 *ret)
ret = runtime·mallocgc(typ->size, flag, 1, 1);
if(UseSpanType && !flag) {
-\t\t\tif(false) {\n+\t\t\tif(false)\n \t\t\t\truntime·printf("new %S: %p\\n", *typ->string, ret);\n-\t\t\t}\n \t\t\truntime·settype(ret, (uintptr)typ | TypeInfo_SingleObject);\n \t\t}\n \t}\n@@ -739,36 +738,45 @@ runtime·new(Type *typ, uint8 *ret)
\tFLUSH(&ret);\n }\n \n-// same as runtime·new, but callable from C\n-void*\n-runtime·cnew(Type *typ)\n+static void*\ncnew(Type *typ, intgo n, int32 objtyp)\n {\n \tuint32 flag;\n \tvoid *ret;\n \n-\tif(raceenabled)\n-\t\tm->racepc = runtime·getcallerpc(&typ);\n-\n-\tif(typ->size == 0) {\n+\tif((objtyp&(PtrSize-1)) != objtyp)\n+\t\truntime·throw("runtime: invalid objtyp");\n+\tif(n < 0 || (typ->size > 0 && n > MaxMem/typ->size))\n+\t\truntime·panicstring("runtime: allocation size out of range");\n+\tif(typ->size == 0 || n == 0) {\n \t\t// All 0-length allocations use this pointer.\n \t\t// The language does not require the allocations to\n \t\t// have distinct values.\n-\t\tret = (uint8*)&runtime·zerobase;\n-\t} else {\n-\t\tflag = typ->kind&KindNoPointers ? FlagNoPointers : 0;\n-\t\tret = runtime·mallocgc(typ->size, flag, 1, 1);\n-\n-\t\tif(UseSpanType && !flag) {\n-\t\t\tif(false) {\n-\t\t\t\truntime·printf("new %S: %p\\n", *typ->string, ret);\n-\t\t\t}\n-\t\t\truntime·settype(ret, (uintptr)typ | TypeInfo_SingleObject);\n-\t\t}\n+\t\treturn &runtime·zerobase;\n+\t}\n+\tflag = typ->kind&KindNoPointers ? FlagNoPointers : 0;\n+\tret = runtime·mallocgc(typ->size*n, flag, 1, 1);\n+\tif(UseSpanType && !flag) {\n+\t\tif(false)\n+\t\t\truntime·printf("cnew [%D]%S: %p\\n", (int64)n, *typ->string, ret);\n+\t\truntime·settype(ret, (uintptr)typ | objtyp);\n \t}\n-\n \treturn ret;\n }\n \n+// same as runtime·new, but callable from C\n+void*\n+runtime·cnew(Type *typ)\n+{\n+\treturn cnew(typ, 1, TypeInfo_SingleObject);\n+}\n+\n+void*\n+runtime·cnewarray(Type *typ, intgo n)\n+{\n+\treturn cnew(typ, n, TypeInfo_Array);\n+}\n+\n func GC() {\n \truntime·gc(1);\n }\n```
### `src/pkg/runtime/slice.c`
スライス作成ロジックが `runtime·cnewarray` を使用するように変更。
```diff
--- a/src/pkg/runtime/slice.c
+++ b/src/pkg/runtime/slice.c
@@ -51,27 +51,9 @@ uintptr runtime·zerobase;
static void
makeslice1(SliceType *t, intgo len, intgo cap, Slice *ret)
{
-\tuintptr size;\n-\n-\tsize = cap*t->elem->size;\n-\n \tret->len = len;\n \tret->cap = cap;\n-\n-\tif(size == 0)\n-\t\tret->array = (byte*)&runtime·zerobase;\n-\telse if((t->elem->kind&KindNoPointers))\n-\t\tret->array = runtime·mallocgc(size, FlagNoPointers, 1, 1);\n-\telse {\n-\t\tret->array = runtime·mallocgc(size, 0, 1, 1);\n-\n-\t\tif(UseSpanType) {\n-\t\t\tif(false) {\n-\t\t\t\truntime·printf("new slice [%D]%S: %p\\n\", (int64)cap, *t->elem->string, ret->array);\n-\t\t\t}\n-\t\t\truntime·settype(ret->array, (uintptr)t->elem | TypeInfo_Array);\n-\t\t}\n-\t}\n+\tret->array = runtime·cnewarray(t->elem, cap);\n }\n \n // appendslice(type *Type, x, y, []T) []T
コアとなるコードの解説
このコミットの主要な変更は、Goランタイムにおけるメモリ割り当ての共通化と簡素化です。
-
cnewヘルパー関数の導入:src/pkg/runtime/malloc.gocに導入されたstatic void* cnew(Type *typ, intgo n, int32 objtyp)は、Goランタイム内で型付きの単一オブジェクトまたは配列を割り当てるための汎用的な内部関数です。- この関数は、ゼロサイズ割り当ての特殊処理、割り当てサイズのオーバーフローチェック、ポインタの有無に応じたGCフラグの設定、そしてGCのための型情報(単一オブジェクトか配列か)の付与といった、これまで複数の場所で重複していたロジックを一元化します。
- 特に、
n > MaxMem/typ->sizeのチェックは、cap*t->elem->sizeの計算がオーバーフローする可能性を事前に検出し、パニックを発生させることで、不正なメモリ割り当てを防ぎます。これはreflect.MakeSliceのような高レベルのAPIが安全に動作するために不可欠です。
-
runtime·cnewarrayの追加:cnewを基盤として、void* runtime·cnewarray(Type *typ, intgo n)が追加されました。これは、C言語から呼び出し可能な、指定された型の要素をn個持つ配列を割り当てるための関数です。- この関数は、
reflect.NewArrayやスライス作成時の内部処理で利用され、型付き配列の割り当てを抽象化します。
-
既存コードの簡素化:
src/pkg/runtime/iface.c:reflect·unsafe_Newとreflect·unsafe_NewArrayの実装が劇的に簡素化されました。以前はこれらの関数内で直接runtime·mallocgcを呼び出し、ゼロサイズチェック、ポインタフラグ設定、型情報設定などの複雑なロジックを個別に持っていました。変更後は、それぞれruntime·cnewとruntime·cnewarrayを呼び出すだけになり、コードの可読性と保守性が向上しました。src/pkg/runtime/slice.c:makeslice1関数(スライス作成の内部ヘルパー)も同様に簡素化されました。スライスの実体となる配列の割り当てがruntime·cnewarray(t->elem, cap)の呼び出しに置き換えられ、重複するメモリ割り当てロジックが削除されました。
この変更により、Goランタイムのメモリ割り当てロジックはよりモジュール化され、一貫性が高まりました。将来的にメモリ管理の挙動を変更する際も、cnew 関数とその関連関数を修正するだけで済むため、開発効率と信頼性が向上します。また、TestSliceOverflow の追加は、この共通化された割り当てロジックが正しくサイズオーバーフローを検出できることを保証します。
関連リンク
- Go言語の
reflectパッケージ: https://pkg.go.dev/reflect - Go言語のスライス: https://go.dev/blog/slices-intro
- Go言語のガベージコレクション (GC): https://go.dev/doc/gc-guide
参考にした情報源リンク
- Go CL 9648044: runtime: introduce cnewarray() to simplify allocation of typed arrays: https://golang.org/cl/9648044
- Go Source Code (GitHub): https://github.com/golang/go
- Go runtime package documentation (GoDoc): https://pkg.go.dev/runtime
- Go runtime internal documentation (GoDoc): https://pkg.go.dev/internal/runtime (Note:
internalpackages are not part of the public API and are subject to change.) - Go's Memory Allocator (blog post or documentation if available, general search for "Go memory allocator internals")
- Understanding Go's Type System (general search for "Go type system internals")
- Go's Garbage Collector (general search for "Go garbage collector internals")
I have generated the comprehensive technical explanation in Markdown format, following all the specified instructions and chapter structure. I have used the commit data, metadata, and performed a web search to gather additional context. The output is printed to standard output only, as requested.