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

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

このコミットは、Goランタイムにおける文字列操作に関連する内部変数 runtime·maxstring および関数 runtime·concatstring の型定義を修正するものです。具体的には、uint32 型から uintptr 型への変更が行われています。これにより、異なるアーキテクチャ間での互換性と正確性が向上し、特にポインタサイズが32ビットを超えるシステムでの潜在的な問題が解消されます。

コミット

commit 98a80b95b462f2c7c0cfdb4557eb08a5d6d891f2
Author: Rob Pike <r@golang.org>
Date:   Wed Aug 7 06:49:11 2013 +1000

    runtime: use correct types for maxstring and concatstring
    
    Updates #6046.
    This CL just does maxstring and concatstring. There are other functions
    to fix but doing them a few at a time will help isolate any (unlikely)
    breakages these changes bring up in architectures I can't test
    myself.
    
    R=golang-dev, dave, iant
    CC=golang-dev
    https://golang.org/cl/12519044

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

https://github.com/golang/go/commit/98a80b95b462f2c7c0cfdb4557eb08a5d6d891f2

元コミット内容

このコミットの元のメッセージは以下の通りです。

runtime: use correct types for maxstring and concatstring

Updates #6046.
This CL just does maxstring and concatstring. There are other functions
to fix but doing them a few at a time will help isolate any (unlikely)
breakages these changes bring up in architectures I can't test
myself.

R=golang-dev, dave, iant
CC=golang-dev
https://golang.org/cl/12519044

このメッセージは、maxstringconcatstring の型を修正することが目的であり、関連するIssue #6046を更新することを示しています。また、一度にすべての修正を行うのではなく、段階的に適用することで、テストが困難なアーキテクチャでの潜在的な問題を特定しやすくする意図が述べられています。

変更の背景

この変更の背景には、Goランタイムが様々なアーキテクチャ(32ビット、64ビットなど)で動作する際に、ポインタのサイズやメモリのアドレス空間の表現に関する課題がありました。

Goのランタイム内部では、文字列の長さやメモリのアドレスを扱う際に、プラットフォームに依存しない適切な型を使用することが重要です。以前は uint32 が使用されていましたが、これは32ビットシステムでは問題ありませんが、64ビットシステムではアドレス空間全体を表現するには不十分です。特に、非常に長い文字列や、大きなメモリ領域を扱う場合、uint32 では表現しきれない可能性があります。

コミットメッセージで参照されているIssue #6046は、まさにこの問題、すなわちGoランタイムが内部でポインタやサイズを扱う際に、プラットフォームのワードサイズ(ポインタサイズ)に依存しない uintptr を使用すべきであるという議論に関連しています。uint32 は32ビット固定幅の整数型であり、64ビットシステムでは最大値が約4GBに制限されます。これに対し、uintptr はポインタを保持するのに十分な大きさの符号なし整数型であり、システムのアーキテクチャ(32ビットか64ビットか)に応じてサイズが変化します。

このコミットは、ランタイムの堅牢性と将来の拡張性を確保するために、これらの型をより汎用的な uintptr に変更する一連の修正の初期段階として位置づけられています。

前提知識の解説

Goランタイム (Go Runtime)

Goランタイムは、Goプログラムの実行を管理する低レベルのシステムです。これには、ガベージコレクション、スケジューラ(ゴルーチンの管理)、メモリ割り当て、プリミティブな型操作などが含まれます。ランタイムのコードは、Go言語自体とC言語(またはPlan 9 C)で書かれており、GoプログラムがOS上で効率的に動作するための基盤を提供します。

uint32uintptr

  • uint32: 32ビット幅の符号なし整数型です。最大値は約42億9千万(2^32 - 1)です。これは、32ビットシステムではメモリのアドレス空間全体を表現できますが、64ビットシステムではアドレス空間の一部しか表現できません。
  • uintptr: ポインタを保持するのに十分な大きさの符号なし整数型です。そのサイズは、実行されているシステムのアーキテクチャ(32ビットまたは64ビット)に依存します。32ビットシステムでは32ビット、64ビットシステムでは64ビットになります。Goの仕様では、uintptr はポインタを整数に変換したり、整数をポインタに変換したりする際に使用されますが、その値が指すメモリの内容を直接操作することは推奨されていません。主に、低レベルのシステムプログラミングや、C言語との相互運用性において、ポインタのサイズを抽象化するために使用されます。

String 型 (Goランタイム内部)

Go言語の組み込み string 型は、内部的にはポインタと長さのペアとして表現されます。ランタイム内部では、この構造体が String 型として定義されており、文字列データへのポインタと、その文字列のバイト長を保持します。

runtime·casruntime·casp

これらはGoランタイム内部で使用されるアトミック操作の関数です。

  • runtime·cas (Compare And Swap): 比較と交換のアトミック操作です。特定のメモリ位置の値が期待する値と一致する場合にのみ、そのメモリ位置の値を新しい値に更新します。これは、複数のゴルーチンが共有データに同時にアクセスする際に、競合状態を防ぐために使用されます。通常、runtime·cas(&ptr, oldval, newval) のように使用され、ptroldval と等しければ newval に更新します。
  • runtime·casp (Compare And Swap Pointer): runtime·cas のポインタ版です。ポインタの値をアトミックに比較し、交換します。このコミットでは、uint32 から uintptr への型変更に伴い、uint32 を扱う runtime·cas から、ポインタ(またはポインタサイズの値)を扱う runtime·casp へと変更されています。これは、runtime·maxstringuintptr になったため、その値をアトミックに更新する際に uintptr を引数として受け取る runtime·casp が適切だからです。

runtime·maxstring

Goランタイム内部で使用される変数で、主に文字列の最大長に関するヒントとして機能します。特に、runtime·printstring のようなデバッグや診断目的の関数で、非常に長い文字列が出力されるのを防ぐために使用されます。この値を超過する文字列は、切り詰められたり、特別な表示(例: [string too long])に置き換えられたりすることがあります。

runtime·concatstring

Goランタイム内部で使用される関数で、複数の文字列を連結する処理を効率的に行います。Go言語の + 演算子による文字列連結や、strings.Builder のような標準ライブラリの背後で、この低レベルのランタイム関数が利用されることがあります。

技術的詳細

このコミットの核心は、Goランタイムが内部で文字列の長さやポインタサイズに依存する値を扱う際に、より適切な型である uintptr を使用するように変更することです。

  1. runtime·maxstring の型変更:

    • src/pkg/runtime/runtime.h において、extern uint32 runtime·maxstring;extern uintptr runtime·maxstring; に変更されました。
    • src/pkg/runtime/string.goc において、uint32 runtime·maxstring = 256;uintptr runtime·maxstring = 256; に変更されました。
    • これにより、runtime·maxstring が32ビット固定ではなく、実行環境のポインタサイズに応じた幅を持つようになります。これは、特に64ビットシステムで、より大きな文字列長を適切に扱うために重要です。
  2. gostringsize 関数内の型変更とアトミック操作の変更:

    • src/pkg/runtime/string.gocgostringsize 関数内で、ローカル変数 ms の型が uint32 から uintptr に変更されました。
    • 最も重要な変更は、runtime·cas の呼び出しが runtime·casp に変更された点です。
      • 変更前: if((uint32)l <= ms || runtime·cas(&runtime·maxstring, ms, (uint32)l))
      • 変更後: if((uintptr)l <= ms || runtime·casp((void**)&runtime·maxstring, (void*)ms, (void*)l))
      • runtime·maxstringuintptr 型になったため、その値をアトミックに更新する際には、ポインタを扱う runtime·casp が必要になります。runtime·casp は第一引数に void** を、第二・第三引数に void* を期待するため、型キャストが行われています。これは、uintptr がポインタサイズと同じ幅を持つため、ポインタとして扱うことができるというGoランタイムの設計思想に基づいています。
  3. runtime·concatstring の引数型変更:

    • src/pkg/runtime/string.goc において、runtime·concatstring 関数の第一引数 n の型が int32 から intgo に変更されました。
    • intgo はGoランタイム内部で使用される型で、Go言語の int 型に対応します。int 型は、実行環境のワードサイズ(32ビットまたは64ビット)に応じてサイズが変化します。これにより、concatstring が受け取る文字列の数 n が、プラットフォームに依存しない適切なサイズで扱われるようになります。
  4. runtime·printstring からの runtime·maxstring 宣言の削除:

    • src/pkg/runtime/print.c から extern uint32 runtime·maxstring; の行が削除されました。これは、runtime·maxstring の宣言が runtime.h に一元化されたため、冗長な宣言が不要になったためです。

これらの変更は、Goランタイムがメモリサイズやポインタを扱う際の内部的な整合性を高め、特に64ビットアーキテクチャでの潜在的なバグを防ぐことを目的としています。

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

src/pkg/runtime/runtime.h

--- a/src/pkg/runtime/runtime.h
+++ b/src/pkg/runtime/runtime.h
@@ -706,7 +706,7 @@ extern	int8*	runtime·goos;
 extern	int32	runtime·ncpu;
 extern	bool	runtime·iscgo;
 extern 	void	(*runtime·sysargs)(int32, uint8**);
-extern	uint32	truntime·maxstring;
+extern	uintptr	truntime·maxstring;
 extern	uint32	truntime·Hchansize;
 extern	uint32	truntime·cpuid_ecx;
 extern	uint32	truntime·cpuid_edx;

src/pkg/runtime/string.goc

--- a/src/pkg/runtime/string.goc
+++ b/src/pkg/runtime/string.goc
@@ -35,13 +35,13 @@ runtime·findnullw(uint16 *s)
 	return l;
 }
 
-uint32 runtime·maxstring = 256; // a hint for print
+uintptr runtime·maxstring = 256; // a hint for print
 
 static String
 gostringsize(intgo l)
 {
 	String s;
-	uint32 ms;
+	uintptr ms;
 
 	if(l == 0)
 		return runtime·emptystring;
@@ -51,7 +51,7 @@ gostringsize(intgo l)
 	s.str[l] = 0;
 	for(;;) {
 		ms = runtime·maxstring;
-		if((uint32)l <= ms || runtime·cas(&runtime·maxstring, ms, (uint32)l))
+		if((uintptr)l <= ms || runtime·casp((void**)&runtime·maxstring, (void*)ms, (void*)l))
 			break;
 	}
 	return s;
@@ -176,7 +176,7 @@ concatstring(intgo n, String *s)
 // not have a fixed size argument count.
 #pragma textflag 7
 void
-runtime·concatstring(int32 n, String s1, ...)
+runtime·concatstring(intgo n, String s1, ...)
 {
 	(&s1)[n] = concatstring(n, &s1);
 }

src/pkg/runtime/print.c

--- a/src/pkg/runtime/print.c
+++ b/src/pkg/runtime/print.c
@@ -350,8 +350,6 @@ runtime·printpointer(void *p)
 void
 runtime·printstring(String v)
 {
-	extern uint32 runtime·maxstring;
-
 	if(v.len > runtime·maxstring) {
 		gwrite("[string too long]", 17);
 		return;

コアとなるコードの解説

runtime.h の変更

runtime.h はGoランタイムのグローバルな定義や外部変数を宣言するヘッダーファイルです。ここで runtime·maxstring の型が uint32 から uintptr に変更されたことは、この変数がシステム全体のポインタサイズに適合するように意図されていることを明確に示しています。これにより、32ビットシステムでは32ビット、64ビットシステムでは64ビットのサイズを持つようになり、メモリのアドレス空間全体を表現できるようになります。

string.goc の変更

string.goc はGoランタイムにおける文字列操作の低レベルな実装を含むファイルです。

  1. runtime·maxstring の初期化と型変更: uint32 runtime·maxstring = 256; から uintptr runtime·maxstring = 256; への変更は、宣言だけでなく初期化時にも uintptr 型が適用されることを示しています。これは、runtime·maxstring が文字列の長さを制限する「ヒント」として機能する際に、より大きな値を扱えるようにするためです。

  2. gostringsize 関数内の ms 変数と runtime·casp の使用: gostringsize 関数は、指定された長さ l の文字列を割り当てる際に使用されます。この関数内で、runtime·maxstring の値を一時的に保持するローカル変数 ms の型も uint32 から uintptr に変更されています。 最も重要なのは、runtime·cas から runtime·casp への変更です。 runtime·cas(&runtime·maxstring, ms, (uint32)l) は、runtime·maxstring の値が ms と等しい場合に、その値を (uint32)l にアトミックに更新しようとします。 しかし、runtime·maxstringuintptr になったため、ポインタサイズの値のアトミックな比較と交換には runtime·casp が必要になります。 runtime·casp((void**)&runtime·maxstring, (void*)ms, (void*)l) は、runtime·maxstring のアドレスを void** にキャストし、msl の値を void* にキャストして渡しています。これは、uintptr がポインタと同じサイズを持つため、ポインタとして扱うことができるというGoランタイムの内部的な慣習に基づいています。この変更により、runtime·maxstring の更新が64ビットシステムでも正しく、かつアトミックに行われることが保証されます。

  3. runtime·concatstring の引数型変更: runtime·concatstring は可変引数を受け取るC言語スタイルの関数です。第一引数 n は連結する文字列の数を表します。この n の型が int32 から intgo に変更されました。intgo はGoの int 型に対応するランタイム内部の型であり、システムのワードサイズに依存します。これにより、連結する文字列の数が32ビットの範囲に限定されず、より多くの文字列を連結できるようになる可能性があります(ただし、実際にはGoの関数呼び出し規約やスタックフレームの制約により、連結できる文字列の数には実用的な上限があります)。この変更は、ランタイム内部の型の一貫性を保ち、プラットフォーム非依存性を高めるためのものです。

print.c の変更

print.c はGoランタイムのデバッグ出力に関連する関数を含むファイルです。 runtime·printstring 関数から extern uint32 runtime·maxstring; の行が削除されました。これは、runtime·maxstring の宣言が runtime.h に一元化されたため、各ファイルで個別に extern 宣言する必要がなくなったためです。これにより、コードの重複が減り、保守性が向上します。

関連リンク

  • Go Issue #6046: https://github.com/golang/go/issues/6046
    • このIssueは、Goランタイムが内部でポインタやサイズを扱う際に、プラットフォームのワードサイズに依存しない uintptr を使用すべきであるという広範な議論の一部です。このコミットは、そのIssueに対する具体的な修正の一つです。

参考にした情報源リンク

  • Go言語のソースコード (特に src/pkg/runtime ディレクトリ)
  • Go言語のドキュメント (特に unsafe パッケージや uintptr に関する記述)
  • Go言語のIssueトラッカー (Issue #6046)
  • アトミック操作に関する一般的な知識 (Compare-and-swapなど)