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

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

このコミットは、Go言語のreflectパッケージにおける配列(特に実行時配列、現在のGoにおけるスライスに相当)の操作能力を拡張し、既存のAPIの命名規則を改善することを目的としています。具体的には、以下のファイルが変更されました。

  • src/lib/reflect/cast_amd64.s: AMD64アーキテクチャ向けのアセンブリコードで、ポインタとアドレス間の変換関数が追加されました。
  • src/lib/reflect/gencast.sh: reflectパッケージのキャスト関数を生成するためのスクリプトで、新しい型が追加されました。
  • src/lib/reflect/test.go: reflectパッケージのテストファイルで、APIの変更と新しい機能のテストが追加されました。
  • src/lib/reflect/value.go: reflectパッケージの主要な型とインターフェースが定義されているファイルで、配列操作に関する機能拡張とAPIの命名変更が行われました。

コミット

commit e4f4ab0b8d2e221909cbf15c6867d4990f09e5a8
Author: Russ Cox <rsc@golang.org>
Date:   Thu Nov 13 13:42:59 2008 -0800

    more array methods
    
    R=r
    OCL=19172
    CL=19172

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

https://github.com/golang/go/commit/e4f4ab0b8d2e221909cbf15c6867d4990f09e5a8

元コミット内容

このコミットの目的は、「より多くの配列メソッド」を追加することです。これは、Go言語のreflectパッケージが、実行時に配列(特に動的な配列、現在のGoにおけるスライス)をより柔軟に操作できるようにするための機能拡張を指しています。

変更の背景

Go言語の初期段階において、reflectパッケージはプログラムの実行時に型情報を検査し、値を操作するための基本的な機能を提供していました。しかし、動的な配列(スライス)のような複雑なデータ構造を効率的かつ安全に操作するためには、さらなる機能が必要とされていました。

このコミットは、特に以下の点に対処するために行われました。

  1. スライス(当時の「実行時配列」)の完全なリフレクションサポート: Goのスライスは、内部的にデータへのポインタ、長さ、容量という3つの要素で構成されています。reflectパッケージがこれらの内部構造にアクセスし、操作できるようにすることで、より高度な汎用コードの記述が可能になります。
  2. APIの一貫性向上: reflectパッケージ内の値設定メソッドの命名がPutからSetに変更されました。これは、Go言語全体のAPI設計における命名規則の統一と一貫性向上の一環であり、より直感的で予測可能なAPIを提供することを目的としています。
  3. 動的な配列の生成: 実行時に新しいスライスを生成し、その長さや容量を設定できる機能は、特にシリアライゼーション/デシリアライゼーション、データ変換、または汎用的なデータ構造を扱うライブラリにおいて不可欠です。

これらの変更は、Go言語がより成熟し、強力なリフレクション機能を持つための重要なステップでした。

前提知識の解説

Go言語のreflectパッケージ

reflectパッケージは、Goプログラムが自身の構造(型、値、関数など)を検査し、実行時に操作するための機能を提供します。これにより、以下のようなことが可能になります。

  • 型の検査: 変数の具体的な型を、コンパイル時ではなく実行時に知ることができます。
  • 値の操作: 変数の値を読み取ったり、変更したりすることができます。
  • 構造体のフィールドへのアクセス: 構造体のフィールド名やタグを読み取り、その値にアクセスできます。
  • 関数の呼び出し: 実行時に任意の関数を呼び出すことができます。

reflectパッケージは、主に汎用的なライブラリ(例:JSONエンコーダ/デコーダ、ORM、テストフレームワーク)を記述する際に使用されますが、その強力さゆえに乱用するとパフォーマンスの低下やコードの複雑化を招く可能性があるため、慎重な使用が求められます。

Go言語の配列とスライス

  • 配列 (Array): Goの配列は、固定長で同じ型の要素のシーケンスです。配列の長さは型の一部であり、コンパイル時に決定されます。例: [5]int は5つの整数を格納する配列です。
  • スライス (Slice): スライスは、Goにおいて最も頻繁に使用される動的な配列です。スライスは配列への参照であり、長さ(len)と容量(cap)を持ちます。長さはスライスに含まれる要素の数、容量は基になる配列が保持できる要素の最大数を示します。スライスは、内部的にはデータへのポインタ、長さ、容量の3つのフィールドを持つ構造体として表現されます。このコミットで言及されている「実行時配列 (RuntimeArray)」は、このスライスの内部表現を指していると考えられます。

アセンブリ言語 (.sファイル)

Go言語の標準ライブラリやランタイムの一部は、パフォーマンスが重要な部分や、Go言語自体では直接アクセスできない低レベルな操作(例:メモリ管理、システムコール、特定のCPU命令の利用)を行うために、アセンブリ言語で記述されています。src/lib/reflect/cast_amd64.sは、AMD64アーキテクチャ上でreflectパッケージが型変換やポインタ操作を行うための低レベルなルーチンを提供します。

Addr型とAddrToPtrX関数

reflectパッケージでは、メモリ上のアドレスを抽象的に表現するためにAddr型が使用されます。AddrToPtrXのような関数は、この抽象的なアドレスを特定のGoの型へのポインタ(例: *int, *string)に変換するために使用されます。これは、Goの型システムを迂回して、生のメモリを操作するためのメカニズムの一部です。

技術的詳細

このコミットは、主にreflectパッケージがGoのスライス(当時の「実行時配列」)をより深く理解し、操作できるようにするための変更と、APIの命名規則の統一を含んでいます。

  1. RuntimeArray構造体の導入: src/lib/reflect/value.goRuntimeArrayという新しい構造体が定義されました。これは、Goのスライスが内部的に持つデータポインタ、長さ、容量を表現するためのものです。

    type RuntimeArray struct {
        data    Addr;   // 実際のデータへのポインタ
        len     uint32; // 要素の数
        cap     uint32; // 割り当てられた要素の最大数
    }
    

    この構造体は、reflectパッケージがスライスの内部表現に直接アクセスし、操作するための橋渡しとなります。

  2. アセンブリ関数によるRuntimeArrayの変換: src/lib/reflect/cast_amd64.sに、Addr型と*RuntimeArray型の間で変換を行う新しいアセンブリ関数が追加されました。

    • reflect·AddrToPtrRuntimeArray: Addr*RuntimeArrayに変換します。
    • reflect·PtrRuntimeArrayToAddr: *RuntimeArrayAddrに変換します。 これらの関数は、Goの型システムを介さずに、低レベルでポインタの型変換を行うために使用されます。
  3. gencast.shの更新: src/lib/reflect/gencast.shスクリプトにRuntimeArrayが追加されました。これにより、reflectパッケージが提供する様々な型変換関数が、RuntimeArray型に対しても自動的に生成されるようになります。

  4. ValueインターフェースのPutからSetへのリネーム: src/lib/reflect/value.goにおいて、IntValue, UintValue, FloatValue, StringValue, BoolValueなどの各種Valueインターフェースに定義されていた値設定メソッドの名前が、PutからSetに変更されました。 例:

    • IntValueインターフェースのPut(int)Set(int)に。
    • StringValueインターフェースのPut(string)Set(string)に。 これは、Go言語のAPI設計における一貫性を高めるための変更であり、より直感的な命名規則を採用しています。
  5. ArrayValueインターフェースとOpenArrayValueStructの機能拡張:

    • ArrayValueインターフェースにCap() intメソッドが追加され、スライスの容量を取得できるようになりました。
    • OpenArrayValueStruct(動的な配列、スライスを表現する内部構造体)にCap()メソッドの実装と、SetLen(len int)メソッドが追加されました。SetLenはスライスの長さを変更する機能を提供しますが、容量を超える長さへの設定はパニックを引き起こします。
    • OpenArrayValueStructの内部で、実際のデータへのポインタと長さ、容量を保持するために*RuntimeArrayフィールドが導入されました。これにより、スライスの内部構造を直接操作できるようになります。
  6. NewOpenArrayValue関数の追加: src/lib/reflect/value.goNewOpenArrayValue(typ ArrayType, len, cap int) ArrayValueという新しいエクスポート関数が追加されました。この関数は、指定された要素型、初期長さ、容量を持つ新しいスライス(ArrayValueとして)を動的に作成することを可能にします。これは、実行時にスライスを生成する必要がある場合に非常に有用です。

  7. テストの更新: src/lib/reflect/test.goでは、PutからSetへのメソッド名変更に対応するテストの修正が行われました。また、新しく追加されたNewOpenArrayValue関数を使用してスライスを生成し、その要素にアクセス・設定するテストケースが追加され、新しいリフレクション機能が正しく動作することを確認しています。

これらの変更により、Goのreflectパッケージは、スライスに対するより強力で柔軟な操作能力を獲得し、Go言語の汎用プログラミングの可能性を広げました。

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

src/lib/reflect/cast_amd64.s

@@ -171,3 +171,13 @@ TEXT reflect·PtrBoolToAddr(SB),7,$-8
 	MOVQ	AX, 16(SP)
 	RET
 
+TEXT reflect·AddrToPtrRuntimeArray(SB),7,$-8
+	MOVQ	8(SP), AX
+	MOVQ	AX, 16(SP)
+	RET
+
+TEXT reflect·PtrRuntimeArrayToAddr(SB),7,$-8
+	MOVQ	8(SP), AX
+	MOVQ	AX, 16(SP)
+	RET

Addr*RuntimeArray間の変換を行うアセンブリ関数が追加されています。

src/lib/reflect/value.go

@@ -14,6 +14,7 @@ import (
 type Addr uint64	// TODO: where are ptrint/intptr etc?
 
 // Conversion functions, implemented in assembler
+type RuntimeArray struct
 func AddrToPtrAddr(Addr) *Addr
 func AddrToPtrInt(Addr) *int
 func AddrToPtrInt8(Addr) *int8
@@ -33,6 +34,8 @@ func AddrToPtrFloat64(Addr) *float64
 func AddrToPtrFloat80(Addr) *float80
 func AddrToPtrString(Addr) *string
 func AddrToPtrBool(Addr) *bool
+func AddrToPtrRuntimeArray(Addr) *RuntimeArray
+func PtrRuntimeArrayToAddr(*RuntimeArray) Addr
 
 export type Empty interface {}	// TODO(r): Delete when no longer needed?
 
@@ -92,7 +95,7 @@ func MissingCreator(typ Type, addr Addr) Value {
 export type IntValue interface {
 	Kind()	int;
 	Get()	int;
-	Put(int);
+	Set(int);
 	Type()	Type;
 }
 
@@ -108,7 +111,7 @@ func (v *IntValueStruct) Get() int {
 	return *AddrToPtrInt(v.addr)
 }
 
-func (v *IntValueStruct) Put(i int) {
+func (v *IntValueStruct) Set(i int) {
 	*AddrToPtrInt(v.addr) = i
 }
 // ... (他のValueインターフェースのPutからSetへの変更が続く)
@@ -533,13 +536,9 @@ export type ArrayValue interface {
 	Type()	Type;
 	Open()	bool;
 	Len()	int;
+\tCap() int;
 	Elem(i int)	Value;
-}\n-\n-type OpenArrayValueStruct struct {
-\tCommon;
-\telemtype	Type;\n-\telemsize	int;\n+\tSetLen(len int);\
 }
 
 /*
@@ -547,20 +546,43 @@ type OpenArrayValueStruct struct {
 		struct	Array {
 			byte*	array;		// actual data
 			uint32	nel;		// number of elements
+\t\t\tuint32	cap;\
 		};
 */
+type RuntimeArray struct {
+	data	Addr;
+	len	uint32;
+	cap	uint32;
+}
+
+type OpenArrayValueStruct struct {
+	Common;
+	elemtype	Type;
+	elemsize	int;
+	array *RuntimeArray;
+}
 
 func (v *OpenArrayValueStruct) Open() bool {
 	return true
 }
 
 func (v *OpenArrayValueStruct) Len() int {
-	return int(*AddrToPtrInt32(v.addr+8));
+	return int(v.array.len);
+}
+
+func (v *OpenArrayValueStruct) Cap() int {
+	return int(v.array.cap);
+}
+
+func (v *OpenArrayValueStruct) SetLen(len int) {
+	if len > v.Cap() {
+		panicln("reflect: OpenArrayValueStruct.SetLen", len, v.Cap());
+	}
+	v.array.len = uint32(len);
 }
 
 func (v *OpenArrayValueStruct) Elem(i int) Value {
-	base := *AddrToPtrAddr(v.addr);
-	return NewValueAddr(v.elemtype, base + Addr(i * v.elemsize));
+	return NewValueAddr(v.elemtype, v.array.data + Addr(i * v.elemsize));
 }
 
 type FixedArrayValueStruct struct {
@@ -578,6 +600,13 @@ func (v *FixedArrayValueStruct) Len() int {
 	return v.len
 }
 
+func (v *FixedArrayValueStruct) Cap() int {
+	return v.len
+}
+
+func (v *FixedArrayValueStruct) SetLen(len int) {
+}
+
 func (v *FixedArrayValueStruct) Elem(i int) Value {
 	return NewValueAddr(v.elemtype, v.addr + Addr(i * v.elemsize));
 	return nil
@@ -592,6 +621,32 @@ func ArrayCreator(typ Type, addr Addr) Value {
 	\tv.typ = typ;
 	\tv.elemtype = arraytype.Elem();
 	\tv.elemsize = v.elemtype.Size();
+\t\tv.array = AddrToPtrRuntimeArray(addr);\
 	\treturn v;
 	}
 \tv := new(FixedArrayValueStruct);
@@ -768,6 +798,32 @@ export func NewInitValue(typ Type) Value {
 	return NewValueAddr(typ, PtrUint8ToAddr(&data[0]));
 }
 
+/*
+	Run-time representation of open arrays looks like this:
+		struct	Array {
+			byte*	array;		// actual data
+			uint32	nel;		// number of elements
+			uint32	cap;		// allocated number of elements
+		};
+*/
+export func NewOpenArrayValue(typ ArrayType, len, cap int) ArrayValue {
+	if !typ.Open() {
+		return nil
+	}
+
+	array := new(RuntimeArray);
+	size := typ.Elem().Size() * cap;
+	if size == 0 {
+		size = 1;
+	}
+	data := new([]uint8, size);
+	array.data = PtrUint8ToAddr(&data[0]);
+	array.len = uint32(len);
+	array.cap = uint32(cap);
+
+	return NewValueAddr(typ, PtrRuntimeArrayToAddr(array));
+}
+
 export func NewValue(e Empty) Value {
 	value, typestring  := sys.reflect(e);
 	p, ok := typecache[typestring];

RuntimeArray構造体の定義、AddrToPtrRuntimeArrayPtrRuntimeArrayToAddrの宣言、PutからSetへのメソッド名変更、ArrayValueインターフェースへのCap()SetLen()の追加、OpenArrayValueStructの内部構造の変更、そしてNewOpenArrayValue関数の追加が行われています。

コアとなるコードの解説

RuntimeArray構造体とアセンブリ関数

RuntimeArray構造体は、Goのスライスが内部的にどのように表現されているかをreflectパッケージが理解するための鍵となります。dataはスライスの基盤となる配列の先頭へのポインタ、lenは現在の要素数、capは割り当てられたメモリの最大容量を示します。

AddrToPtrRuntimeArrayPtrRuntimeArrayToAddrというアセンブリ関数は、Goの型システムをバイパスして、生のメモリアドレス(Addr)とRuntimeArray構造体へのポインタ(*RuntimeArray)の間で安全に変換を行うために存在します。これにより、reflectパッケージはスライスの内部表現に直接アクセスし、その長さや容量、基盤となるデータポインタを読み書きできるようになります。これは、Goの型安全性を維持しつつ、低レベルな操作を可能にするための重要なメカニズムです。

PutからSetへのメソッド名変更

これは、reflectパッケージのAPI全体における命名規則の統一を目的とした変更です。Putという名前は「値を置く」というニュアンスを持ちますが、Setはより明確に「値を設定する」という動作を示します。この変更により、reflectパッケージのAPIがより一貫性のあるものとなり、開発者にとって理解しやすくなりました。

ArrayValueインターフェースとOpenArrayValueStructの拡張

ArrayValueインターフェースにCap()メソッドが追加されたことで、reflectパッケージを通じてスライスの容量をプログラム的に取得できるようになりました。これは、スライスのメモリ割り当て状況を把握したり、容量に基づいた最適化を行ったりする際に役立ちます。

OpenArrayValueStructは、動的な配列(スライス)のreflect.Value表現を内部的に管理する構造体です。この構造体が*RuntimeArrayフィールドを持つように変更されたことで、reflectパッケージはスライスの内部的な長さ、容量、データポインタに直接アクセスできるようになりました。

SetLen(len int)メソッドの追加は、reflectパッケージを通じてスライスの長さを動的に変更する機能を提供します。ただし、この操作はスライスの容量を超える長さを設定しようとするとパニックを引き起こすため、安全な使用が求められます。

NewOpenArrayValue関数

この関数は、reflectパッケージの最も重要な追加機能の一つです。これにより、開発者は実行時に新しいスライスを動的に作成し、その初期長さと容量を指定できるようになります。これは、例えば、JSONやデータベースから読み込んだデータに基づいて、適切なサイズのスライスを動的に構築するようなシナリオで非常に強力な機能となります。この関数は、Goの型システムがコンパイル時に型を決定するのに対し、実行時に柔軟なデータ構造を生成する能力をreflectパッケージに与えます。

これらの変更は、Go言語がその初期段階において、リフレクション機能をより堅牢で実用的なものにするための基盤を築いたことを示しています。特にスライスのようなGoのコアデータ構造に対する深いリフレクションサポートは、Goエコシステムにおける多くの汎用ライブラリの発展に貢献しました。

関連リンク

参考にした情報源リンク

  • GitHubコミットページ: https://github.com/golang/go/commit/e4f4ab0b8d2e221909cbf15c6867d4990f09e5a8
  • Go言語の初期の設計に関する議論やドキュメント (当時の文脈を理解するため): Go言語の公式リポジトリの歴史的なコミットログや、Goの設計に関する初期のメーリングリストのアーカイブなどが参考になりますが、特定のURLは提供できません。
  • Go言語のアセンブリに関するドキュメント: https://go.dev/doc/asm
  • Go言語の内部構造に関する情報 (スライスの内部表現など): Goのソースコード自体が最も正確な情報源ですが、Goの内部構造を解説するブログ記事や書籍も参考になります。```markdown

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

このコミットは、Go言語のreflectパッケージにおける配列(特に実行時配列、現在のGoにおけるスライスに相当)の操作能力を拡張し、既存のAPIの命名規則を改善することを目的としています。具体的には、以下のファイルが変更されました。

  • src/lib/reflect/cast_amd64.s: AMD64アーキテクチャ向けのアセンブリコードで、ポインタとアドレス間の変換関数が追加されました。
  • src/lib/reflect/gencast.sh: reflectパッケージのキャスト関数を生成するためのスクリプトで、新しい型が追加されました。
  • src/lib/reflect/test.go: reflectパッケージのテストファイルで、APIの変更と新しい機能のテストが追加されました。
  • src/lib/reflect/value.go: reflectパッケージの主要な型とインターフェースが定義されているファイルで、配列操作に関する機能拡張とAPIの命名変更が行われました。

コミット

commit e4f4ab0b8d2e221909cbf15c6867d4990f09e5a8
Author: Russ Cox <rsc@golang.org>
Date:   Thu Nov 13 13:42:59 2008 -0800

    more array methods
    
    R=r
    OCL=19172
    CL=19172

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

https://github.com/golang/go/commit/e4f4ab0b8d2e221909cbf15c6867d4990f09e5a8

元コミット内容

このコミットの目的は、「より多くの配列メソッド」を追加することです。これは、Go言語のreflectパッケージが、実行時に配列(特に動的な配列、現在のGoにおけるスライス)をより柔軟に操作できるようにするための機能拡張を指しています。

変更の背景

Go言語の初期段階において、reflectパッケージはプログラムの実行時に型情報を検査し、値を操作するための基本的な機能を提供していました。しかし、動的な配列(スライス)のような複雑なデータ構造を効率的かつ安全に操作するためには、さらなる機能が必要とされていました。

このコミットは、特に以下の点に対処するために行われました。

  1. スライス(当時の「実行時配列」)の完全なリフレクションサポート: Goのスライスは、内部的にデータへのポインタ、長さ、容量という3つの要素で構成されています。reflectパッケージがこれらの内部構造にアクセスし、操作できるようにすることで、より高度な汎用コードの記述が可能になります。
  2. APIの一貫性向上: reflectパッケージ内の値設定メソッドの命名がPutからSetに変更されました。これは、Go言語全体のAPI設計における命名規則の統一と一貫性向上の一環であり、より直感的で予測可能なAPIを提供することを目的としています。
  3. 動的な配列の生成: 実行時に新しいスライスを生成し、その長さや容量を設定できる機能は、特にシリアライゼーション/デシリアライゼーション、データ変換、または汎用的なデータ構造を扱うライブラリにおいて不可欠です。

これらの変更は、Go言語がより成熟し、強力なリフレクション機能を持つための重要なステップでした。

前提知識の解説

Go言語のreflectパッケージ

reflectパッケージは、Goプログラムが自身の構造(型、値、関数など)を検査し、実行時に操作するための機能を提供します。これにより、以下のようなことが可能になります。

  • 型の検査: 変数の具体的な型を、コンパイル時ではなく実行時に知ることができます。
  • 値の操作: 変数の値を読み取ったり、変更したりすることができます。
  • 構造体のフィールドへのアクセス: 構造体のフィールド名やタグを読み取り、その値にアクセスできます。
  • 関数の呼び出し: 実行時に任意の関数を呼び出すことができます。

reflectパッケージは、主に汎用的なライブラリ(例:JSONエンコーダ/デコーダ、ORM、テストフレームワーク)を記述する際に使用されますが、その強力さゆえに乱用するとパフォーマンスの低下やコードの複雑化を招く可能性があるため、慎重な使用が求められます。

Go言語の配列とスライス

  • 配列 (Array): Goの配列は、固定長で同じ型の要素のシーケンスです。配列の長さは型の一部であり、コンパイル時に決定されます。例: [5]int は5つの整数を格納する配列です。
  • スライス (Slice): スライスは、Goにおいて最も頻繁に使用される動的な配列です。スライスは配列への参照であり、長さ(len)と容量(cap)を持ちます。長さはスライスに含まれる要素の数、容量は基になる配列が保持できる要素の最大数を示します。スライスは、内部的にはデータへのポインタ、長さ、容量の3つのフィールドを持つ構造体として表現されます。このコミットで言及されている「実行時配列 (RuntimeArray)」は、このスライスの内部表現を指していると考えられます。

アセンブリ言語 (.sファイル)

Go言語の標準ライブラリやランタイムの一部は、パフォーマンスが重要な部分や、Go言語自体では直接アクセスできない低レベルな操作(例:メモリ管理、システムコール、特定のCPU命令の利用)を行うために、アセンブリ言語で記述されています。src/lib/reflect/cast_amd64.sは、AMD64アーキテクチャ上でreflectパッケージが型変換やポインタ操作を行うための低レベルなルーチンを提供します。

Addr型とAddrToPtrX関数

reflectパッケージでは、メモリ上のアドレスを抽象的に表現するためにAddr型が使用されます。AddrToPtrXのような関数は、この抽象的なアドレスを特定のGoの型へのポインタ(例: *int, *string)に変換するために使用されます。これは、Goの型システムを迂回して、生のメモリを操作するためのメカニズムの一部です。

技術的詳細

このコミットは、主にreflectパッケージがGoのスライス(当時の「実行時配列」)をより深く理解し、操作できるようにするための変更と、APIの命名規則の統一を含んでいます。

  1. RuntimeArray構造体の導入: src/lib/reflect/value.goRuntimeArrayという新しい構造体が定義されました。これは、Goのスライスが内部的に持つデータポインタ、長さ、容量を表現するためのものです。

    type RuntimeArray struct {
        data    Addr;   // 実際のデータへのポインタ
        len     uint32; // 要素の数
        cap     uint32; // 割り当てられた要素の最大数
    }
    

    この構造体は、reflectパッケージがスライスの内部表現に直接アクセスし、操作するための橋渡しとなります。

  2. アセンブリ関数によるRuntimeArrayの変換: src/lib/reflect/cast_amd64.sに、Addr型と*RuntimeArray型の間で変換を行う新しいアセンブリ関数が追加されました。

    • reflect·AddrToPtrRuntimeArray: Addr*RuntimeArrayに変換します。
    • reflect·PtrRuntimeArrayToAddr: *RuntimeArrayAddrに変換します。 これらの関数は、Goの型システムを介さずに、低レベルでポインタの型変換を行うために使用されます。
  3. gencast.shの更新: src/lib/reflect/gencast.shスクリプトにRuntimeArrayが追加されました。これにより、reflectパッケージが提供する様々な型変換関数が、RuntimeArray型に対しても自動的に生成されるようになります。

  4. ValueインターフェースのPutからSetへのリネーム: src/lib/reflect/value.goにおいて、IntValue, UintValue, FloatValue, StringValue, BoolValueなどの各種Valueインターフェースに定義されていた値設定メソッドの名前が、PutからSetに変更されました。 例:

    • IntValueインターフェースのPut(int)Set(int)に。
    • StringValueインターフェースのPut(string)Set(string)に。 これは、Go言語のAPI設計における一貫性を高めるための変更であり、より直感的な命名規則を採用しています。
  5. ArrayValueインターフェースとOpenArrayValueStructの機能拡張:

    • ArrayValueインターフェースにCap() intメソッドが追加され、スライスの容量を取得できるようになりました。
    • OpenArrayValueStruct(動的な配列、スライスを表現する内部構造体)にCap()メソッドの実装と、SetLen(len int)メソッドが追加されました。SetLenはスライスの長さを変更する機能を提供しますが、容量を超える長さへの設定はパニックを引き起こします。
    • OpenArrayValueStructの内部で、実際のデータへのポインタと長さ、容量を保持するために*RuntimeArrayフィールドが導入されました。これにより、スライスの内部構造を直接操作できるようになります。
  6. NewOpenArrayValue関数の追加: src/lib/reflect/value.goNewOpenArrayValue(typ ArrayType, len, cap int) ArrayValueという新しいエクスポート関数が追加されました。この関数は、指定された要素型、初期長さ、容量を持つ新しいスライス(ArrayValueとして)を動的に作成することを可能にします。これは、実行時にスライスを生成する必要がある場合に非常に有用です。

  7. テストの更新: src/lib/reflect/test.goでは、PutからSetへのメソッド名変更に対応するテストの修正が行われました。また、新しく追加されたNewOpenArrayValue関数を使用してスライスを生成し、その要素にアクセス・設定するテストケースが追加され、新しいリフレクション機能が正しく動作することを確認しています。

これらの変更により、Goのreflectパッケージは、スライスに対するより強力で柔軟な操作能力を獲得し、Go言語の汎用プログラミングの可能性を広げました。

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

src/lib/reflect/cast_amd64.s

@@ -171,3 +171,13 @@ TEXT reflect·PtrBoolToAddr(SB),7,$-8
 	MOVQ	AX, 16(SP)
 	RET
 
+TEXT reflect·AddrToPtrRuntimeArray(SB),7,$-8
+	MOVQ	8(SP), AX
+	MOVQ	AX, 16(SP)
+	RET
+
+TEXT reflect·PtrRuntimeArrayToAddr(SB),7,$-8
+	MOVQ	8(SP), AX
+	MOVQ	AX, 16(SP)
+	RET

Addr*RuntimeArray間の変換を行うアセンブリ関数が追加されています。

src/lib/reflect/value.go

@@ -14,6 +14,7 @@ import (
 type Addr uint64	// TODO: where are ptrint/intptr etc?
 
 // Conversion functions, implemented in assembler
+type RuntimeArray struct
 func AddrToPtrAddr(Addr) *Addr
 func AddrToPtrInt(Addr) *int
 func AddrToPtrInt8(Addr) *int8
@@ -33,6 +34,8 @@ func AddrToPtrFloat64(Addr) *float64
 func AddrToPtrFloat80(Addr) *float80
 func AddrToPtrString(Addr) *string
 func AddrToPtrBool(Addr) *bool
+func AddrToPtrRuntimeArray(Addr) *RuntimeArray
+func PtrRuntimeArrayToAddr(*RuntimeArray) Addr
 
 export type Empty interface {}	// TODO(r): Delete when no longer needed?
 
@@ -92,7 +95,7 @@ func MissingCreator(typ Type, addr Addr) Value {
 export type IntValue interface {
 	Kind()	int;
 	Get()	int;
-	Put(int);
+	Set(int);
 	Type()	Type;
 }
 
@@ -108,7 +111,7 @@ func (v *IntValueStruct) Get() int {
 	return *AddrToPtrInt(v.addr)
 }
 
-func (v *IntValueStruct) Put(i int) {
+func (v *IntValueStruct) Set(i int) {
 	*AddrToPtrInt(v.addr) = i
 }
 // ... (他のValueインターフェースのPutからSetへの変更が続く)
@@ -533,13 +536,9 @@ export type ArrayValue interface {
 	Type()	Type;
 	Open()	bool;
 	Len()	int;
+\tCap() int;
 	Elem(i int)	Value;
-}\n-\n-type OpenArrayValueStruct struct {
-\tCommon;
-\telemtype	Type;\
-\telemsize	int;\
+\tSetLen(len int);\
 }
 
 /*
@@ -547,20 +546,43 @@ type OpenArrayValueStruct struct {
 		struct	Array {
 			byte*	array;		// actual data
 			uint32	nel;		// number of elements
+\t\t\tuint32	cap;\
 		};
 */
+type RuntimeArray struct {
+	data	Addr;
+	len	uint32;
+	cap	uint32;
+}
+
+type OpenArrayValueStruct struct {
+	Common;
+	elemtype	Type;
+	elemsize	int;
+	array *RuntimeArray;
+}
 
 func (v *OpenArrayValueStruct) Open() bool {
 	return true
 }
 
 func (v *OpenArrayValueStruct) Len() int {
-	return int(*AddrToPtrInt32(v.addr+8));
+	return int(v.array.len);
+}
+
+func (v *OpenArrayValueStruct) Cap() int {
+	return int(v.array.cap);
+}
+
+func (v *OpenArrayValueStruct) SetLen(len int) {
+	if len > v.Cap() {
+		panicln("reflect: OpenArrayValueStruct.SetLen", len, v.Cap());
+	}
+	v.array.len = uint32(len);
 }
 
 func (v *OpenArrayValueStruct) Elem(i int) Value {
-	base := *AddrToPtrAddr(v.addr);
-	return NewValueAddr(v.elemtype, base + Addr(i * v.elemsize));
+	return NewValueAddr(v.elemtype, v.array.data + Addr(i * v.elemsize));
 }
 
 type FixedArrayValueStruct struct {
@@ -578,6 +600,13 @@ func (v *FixedArrayValueStruct) Len() int {
 	return v.len
 }
 
+func (v *FixedArrayValueStruct) Cap() int {
+	return v.len
+}
+
+func (v *FixedArrayValueStruct) SetLen(len int) {
+}
+
 func (v *FixedArrayValueStruct) Elem(i int) Value {
 	return NewValueAddr(v.elemtype, v.addr + Addr(i * v.elemsize));
 	return nil
@@ -592,6 +621,32 @@ func ArrayCreator(typ Type, addr Addr) Value {
 	\tv.typ = typ;
 	\tv.elemtype = arraytype.Elem();
 	\tv.elemsize = v.elemtype.Size();
+\t\tv.array = AddrToPtrRuntimeArray(addr);\
 	\treturn v;
 	}
 \tv := new(FixedArrayValueStruct);
@@ -768,6 +798,32 @@ export func NewInitValue(typ Type) Value {
 	return NewValueAddr(typ, PtrUint8ToAddr(&data[0]));
 }
 
+/*
+	Run-time representation of open arrays looks like this:
+		struct	Array {
+			byte*	array;		// actual data
+			uint32	nel;		// number of elements
+			uint32	cap;		// allocated number of elements
+		};
+*/
+export func NewOpenArrayValue(typ ArrayType, len, cap int) ArrayValue {
+	if !typ.Open() {
+		return nil
+	}
+
+	array := new(RuntimeArray);
+	size := typ.Elem().Size() * cap;
+	if size == 0 {
+		size = 1;
+	}
+	data := new([]uint8, size);
+	array.data = PtrUint8ToAddr(&data[0]);
+	array.len = uint32(len);
+	array.cap = uint32(cap);
+
+	return NewValueAddr(typ, PtrRuntimeArrayToAddr(array));
+}
+
 export func NewValue(e Empty) Value {
 	value, typestring  := sys.reflect(e);
 	p, ok := typecache[typestring];

RuntimeArray構造体の定義、AddrToPtrRuntimeArrayPtrRuntimeArrayToAddrの宣言、PutからSetへのメソッド名変更、ArrayValueインターフェースへのCap()SetLen()の追加、OpenArrayValueStructの内部構造の変更、そしてNewOpenArrayValue関数の追加が行われています。

コアとなるコードの解説

RuntimeArray構造体とアセンブリ関数

RuntimeArray構造体は、Goのスライスが内部的にどのように表現されているかをreflectパッケージが理解するための鍵となります。dataはスライスの基盤となる配列の先頭へのポインタ、lenは現在の要素数、capは割り当てられたメモリの最大容量を示します。

AddrToPtrRuntimeArrayPtrRuntimeArrayToAddrというアセンブリ関数は、Goの型システムをバイパスして、生のメモリアドレス(Addr)とRuntimeArray構造体へのポインタ(*RuntimeArray)の間で安全に変換を行うために存在します。これにより、reflectパッケージはスライスの内部表現に直接アクセスし、その長さや容量、基盤となるデータポインタを読み書きできるようになります。これは、Goの型安全性を維持しつつ、低レベルな操作を可能にするための重要なメカニズムです。

PutからSetへのメソッド名変更

これは、reflectパッケージのAPI全体における命名規則の統一を目的とした変更です。Putという名前は「値を置く」というニュアンスを持ちますが、Setはより明確に「値を設定する」という動作を示します。この変更により、reflectパッケージのAPIがより一貫性のあるものとなり、開発者にとって理解しやすくなりました。

ArrayValueインターフェースとOpenArrayValueStructの拡張

ArrayValueインターフェースにCap()メソッドが追加されたことで、reflectパッケージを通じてスライスの容量をプログラム的に取得できるようになりました。これは、スライスのメモリ割り当て状況を把握したり、容量に基づいた最適化を行ったりする際に役立ちます。

OpenArrayValueStructは、動的な配列(スライス)のreflect.Value表現を内部的に管理する構造体です。この構造体が*RuntimeArrayフィールドを持つように変更されたことで、reflectパッケージはスライスの内部的な長さ、容量、データポインタに直接アクセスできるようになりました。

SetLen(len int)メソッドの追加は、reflectパッケージを通じてスライスの長さを動的に変更する機能を提供します。ただし、この操作はスライスの容量を超える長さを設定しようとするとパニックを引き起こすため、安全な使用が求められます。

NewOpenArrayValue関数

この関数は、reflectパッケージの最も重要な追加機能の一つです。これにより、開発者は実行時に新しいスライスを動的に作成し、その初期長さと容量を指定できるようになります。これは、例えば、JSONやデータベースから読み込んだデータに基づいて、適切なサイズのスライスを動的に構築するようなシナリオで非常に強力な機能となります。この関数は、Goの型システムがコンパイル時に型を決定するのに対し、実行時に柔軟なデータ構造を生成する能力をreflectパッケージに与えます。

これらの変更は、Go言語がその初期段階において、リフレクション機能をより堅牢で実用的なものにするための基盤を築いたことを示しています。特にスライスのようなGoのコアデータ構造に対する深いリフレクションサポートは、Goエコシステムにおける多くの汎用ライブラリの発展に貢献しました。

関連リンク

参考にした情報源リンク

  • GitHubコミットページ: https://github.com/golang/go/commit/e4f4ab0b8d2e221909cbf15c6867d4990f09e5a8
  • Go言語の初期の設計に関する議論やドキュメント (当時の文脈を理解するため): Go言語の公式リポジトリの歴史的なコミットログや、Goの設計に関する初期のメーリングリストのアーカイブなどが参考になりますが、特定のURLは提供できません。
  • Go言語のアセンブリに関するドキュメント: https://go.dev/doc/asm
  • Go言語の内部構造に関する情報 (スライスの内部表現など): Goのソースコード自体が最も正確な情報源ですが、Goの内部構造を解説するブログ記事や書籍も参考になります。