[インデックス 13946] ファイルの概要
このコミットは、Go言語のint
およびuint
型が、AMD64(x86-64)アーキテクチャ上で32ビットから64ビットに拡張されたことを記録しています。これにより、64ビットシステム上でのGoプログラムがより大きなメモリ空間を扱えるようになり、特に20億以上の要素を持つスライスの割り当てが可能になります。この変更は、Go 1.1リリースの一部として導入されました。
コミット
commit 10ea6519e4e61d47385ca7b7f60ca96856271de7
Author: Russ Cox <rsc@golang.org>
Date: Mon Sep 24 20:57:01 2012 -0400
build: make int 64 bits on amd64
The assembly offsets were converted mechanically using
code.google.com/p/rsc/cmd/asmlint. The instruction
changes were done by hand.
Fixes #2188.
R=iant, r, bradfitz, remyoudompheng
CC=golang-dev
https://golang.org/cl/6550058
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/10ea6519e4e61d47385ca7b7f60ca96856271de7
元コミット内容
build: make int 64 bits on amd64
The assembly offsets were converted mechanically using
code.google.com/p/rsc/cmd/asmlint. The instruction
changes were done by hand.
Fixes #2188.
R=iant, r, bradfitz, remyoudompheng
CC=golang-dev
https://golang.org/cl/6550058
変更の背景
Go言語のint
およびuint
型は、そのサイズが実装に依存すると規定されています。Go 1.1より前のバージョンでは、64ビットシステム(AMD64など)であってもこれらの型は32ビットとして扱われていました。これは、Goが設計された当初の哲学や、クロスプラットフォームでの一貫性を保つための選択であったと考えられます。
しかし、32ビットのint
型にはいくつかの制約がありました。最も顕著なのは、配列やスライスのインデックスとして使用される際に、2^31-1(約20億)を超える要素数を直接扱うことができない点です。現代の64ビットシステムでは、より大きなメモリ空間を扱うことが一般的であり、この32ビットの制限は大規模なデータ構造を扱うアプリケーションにとってボトルネックとなる可能性がありました。
このコミットは、Go 1.1のリリースに向けて、AMD64のような64ビットプラットフォーム上でint
とuint
を64ビットに拡張することで、この制約を解消し、Goプログラムが64ビットシステムの能力を最大限に活用できるようにすることを目的としています。これにより、20億以上の要素を持つスライスの割り当てが可能になり、より大規模なデータ処理やメモリ管理が効率的に行えるようになります。
コミットメッセージにある「Fixes #2188」は、この変更がGoのIssue 2188を解決したことを示しています。このIssueは、int
型のサイズに関する議論や、それがもたらす影響についてのものであったと推測されます。
前提知識の解説
Go言語のint
およびuint
型
Go言語において、int
とuint
はプラットフォームに依存する整数型です。これは、コンパイラが動作するシステムのワードサイズ(CPUが一度に処理できるデータの単位)に合わせて、これらの型のサイズを決定することを意味します。例えば、32ビットシステムでは32ビット、64ビットシステムでは64ビットになります。この柔軟性により、Goは異なるアーキテクチャ間で効率的なコードを生成できますが、同時にプログラマは特定のビット幅に依存するコードを書く際には注意が必要です。明示的なビット幅を持つ型(int32
, int64
, uint32
, uint64
など)も提供されており、これらを使用することで移植性の高いコードを書くことができます。
AMD64/x86-64アーキテクチャ
AMD64(またはx86-64)は、64ビットの命令セットアーキテクチャであり、Intelのx86アーキテクチャの64ビット拡張です。現代のほとんどのデスクトップPCやサーバーで使用されています。このアーキテクチャは、64ビットのレジスタとアドレス空間を提供し、これにより32ビットシステムよりもはるかに大きなメモリ(理論上は18エクサバイト)を直接アドレス指定できます。
アセンブリ言語とスタックフレーム
アセンブリ言語は、CPUが直接実行できる機械語に非常に近い低レベルのプログラミング言語です。Go言語のランタイムや一部の標準ライブラリは、パフォーマンスが重要な部分でアセンブリ言語で記述されています。
スタックフレームは、関数が呼び出されたときにスタック上に確保されるメモリ領域です。これには、関数のローカル変数、引数、戻りアドレスなどが含まれます。アセンブリコードでは、これらの要素にアクセスするために、ベースポインタ(BP)やスタックポインタ(SP)からのオフセットを使用します。例えば、s+8(FP)
のような表記は、フレームポインタ(FP)から8バイトオフセットした位置にあるスタック上の変数s
を指します。
Goのツールチェイン(gc
, gccgo
, cgo
)
gc
: Go言語の公式コンパイラであり、Goのソースコードを機械語にコンパイルします。Goの標準的な開発環境で使用されます。gccgo
: GCC(GNU Compiler Collection)をベースにしたGoコンパイラです。gc
とは異なる実装ですが、Go言語の仕様に準拠しています。cgo
: GoプログラムからC言語のコードを呼び出すためのツールです。C言語の型とGo言語の型の間でデータの変換を行う役割も担います。
これらのツールは、Goプログラムのビルドプロセスにおいて、型のサイズやアセンブリコードの生成に密接に関わっています。
技術的詳細
このコミットの主要な技術的変更は、AMD64アーキテクチャにおけるint
およびuint
型のサイズを32ビットから64ビットに変更することです。この変更は、Goコンパイラ(gc
およびgccgo
)、リンカ、およびアセンブリ言語で書かれた標準ライブラリのコードに広範な影響を与えます。
-
型定義の変更:
src/cmd/6g/galign.c
:gc
コンパイラの一部であり、型の配置とアラインメントを定義します。ここでTINT
とTUINT
がTINT32
/TUINT32
からTINT64
/TUINT64
に変更され、widthint
(int
型の幅)が4バイトから8バイトに更新されています。src/cmd/6l/l.h
:6l
リンカのヘッダーファイルで、IntSize
が4から8に変更されています。これは、リンカがint
型を扱う際のサイズ認識を更新します。src/cmd/cgo/main.go
:cgo
ツールにおいて、intSizeMap
のamd64
エントリが4から8に変更されています。これにより、C言語との相互運用においてint
型のサイズが正しく認識されるようになります。src/cmd/dist/goc2c.c
:use64bitint
フラグが0から1に変更されています。これは、Goのビルドシステムが64ビットint
を使用することを指示します。src/pkg/runtime/runtime.h
: Goランタイムのヘッダーファイルで、intgo
とuintgo
(Goのint
とuint
に対応する内部型)の定義がint32
/uint32
からint64
/uint64
に変更されています。これは、ランタイムレベルでint
型のサイズが64ビットとして扱われることを保証します。
-
アセンブリコードの変更:
int
型のサイズが変更されたことで、アセンブリ言語で書かれた関数におけるスタックフレームのレイアウトと、レジスタ操作の命令が影響を受けます。- スタックオフセットの調整: 関数引数や戻り値がスタックに配置される際のアドレスオフセットが変更されます。例えば、
s+8(FP)
がs+24(FP)
に、r+24(FP)
がr+32(FP)
に変更されるなど、多くのオフセットが調整されています。これは、int
型が32ビットから64ビットになったことで、スタック上の変数の占めるバイト数が増加したためです。 - 命令の変更: 32ビット操作を行う命令(例:
MOVL
、CMPL
、DECL
、ADDL
、SUBL
)が、対応する64ビット操作を行う命令(例:MOVQ
、CMPQ
、DECQ
、ADDQ
、SUBQ
)に変更されています。これは、int
型が64ビットになったため、レジスタやメモリとの間で64ビットデータを扱う必要があるためです。src/pkg/bytes/asm_amd64.s
:IndexByte
やEqual
といったバイト操作関数で、引数のオフセットや比較命令が変更されています。src/pkg/hash/crc32/crc32_amd64.s
: CRC32計算関数で、長さの比較やデクリメント命令が変更されています。src/pkg/math/big/arith_amd64.s
: 多倍長整数演算を行う関数(addVV
,subVV
,addVW
,subVW
,shlVU
,shrVU
,mulAddVWW
,addMulVVW
,divWVW
,bitLen
)で、引数のオフセット、レジスタ操作、比較命令などが広範囲にわたって変更されています。特に、MOVL
からMOVQ
への変更が顕著です。
- スタックオフセットの調整: 関数引数や戻り値がスタックに配置される際のアドレスオフセットが変更されます。例えば、
-
ドキュメントの更新:
doc/go1.1.html
: Go 1.1のリリースノートに、int
型のサイズ変更に関するセクションが追加されています。このセクションでは、変更の概要、ほとんどのプログラムへの影響のなさ、そして32ビットの仮定に依存するコードの挙動の変化(例:int(uint32(0))
の挙動)について説明されています。doc/go_faq.html
: FAQドキュメントのint
型のサイズに関する項目が更新され、Go 1.1以降は64ビットシステムで64ビットになることが明記されています。
-
テストの更新:
test/index.go
: テストスイートに、i64big
やi64bigger
といったテストケースが追加または修正されています。これらは、int
型が64ビットになったことで、以前はコンパイルエラーになっていた大きな数値がAMD64上で正しく扱われることを検証するためのものです。
これらの変更は、Go言語のコア部分に深く関わるものであり、Goのコンパイラ、リンカ、ランタイム、標準ライブラリ、そしてドキュメントに至るまで、多岐にわたる調整が必要とされました。アセンブリコードのオフセット変更はasmlint
というツールで機械的に変換され、命令の変更は手動で行われたとコミットメッセージに記載されており、この作業の複雑さを示しています。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、主に以下のファイルに集中しています。
-
src/cmd/6g/galign.c
:--- a/src/cmd/6g/galign.c +++ b/src/cmd/6g/galign.c @@ -17,8 +17,8 @@ vlong MAXWIDTH = 1LL<<50; */ Typedef typedefs[] = { - "int", TINT, TINT32, - "uint", TUINT, TUINT32, + "int", TINT, TINT64, + "uint", TUINT, TUINT64, "uintptr", TUINTPTR, TUINT64, 0 }; @@ -27,7 +27,7 @@ void betyperinit(void) { widthptr = 8; - widthint = 4; + widthint = 8; zprog.link = P; zprog.as = AGOK;
-
src/pkg/runtime/runtime.h
:--- a/src/pkg/runtime/runtime.h +++ b/src/pkg/runtime/runtime.h @@ -19,8 +19,8 @@ typedef double float64; #ifdef _64BIT typedef uint64 uintptr; typedef int64 intptr; -typedef int32 intgo; // Go's int -typedef uint32 uintgo; // Go's uint +typedef int64 intgo; // Go's int +typedef uint64 uintgo; // Go's uint #else typedef uint32 uintptr; typedef int32 intptr;
-
src/pkg/math/big/arith_amd64.s
(抜粋):--- a/src/pkg/math/big/arith_amd64.s +++ b/src/pkg/math/big/arith_amd64.s @@ -36,9 +36,9 @@ TEXT ·divWW(SB),7,$0 // func addVV(z, x, y []Word) (c Word) TEXT ·addVV(SB),7,$0 - MOVL z+8(FP), DI - MOVQ x+16(FP), R8 - MOVQ y+32(FP), R9 + MOVQ z+8(FP), DI + MOVQ x+24(FP), R8 + MOVQ y+48(FP), R9 MOVQ z+0(FP), R10 MOVQ $0, CX // c = 0 @@ -83,16 +83,16 @@ L1: // n > 0 SUBQ $1, DI // n-- JG L1 // if n > 0 goto L1 -E1: MOVQ CX, c+48(FP) // return c +E1: MOVQ CX, c+72(FP) // return c RET // func subVV(z, x, y []Word) (c Word) // (same as addVV except for SBBQ instead of ADCQ and label names) TEXT ·subVV(SB),7,$0 - MOVL z+8(FP), DI - MOVQ x+16(FP), R8 - MOVQ y+32(FP), R9 + MOVQ z+8(FP), DI + MOVQ x+24(FP), R8 + MOVQ y+48(FP), R9 MOVQ z+0(FP), R10 MOVQ $0, CX // c = 0
コアとなるコードの解説
src/cmd/6g/galign.c
このファイルはGoコンパイラ(gc
)の一部であり、Goの型がメモリ上でどのようにアラインされ、どれくらいのサイズを占めるかを定義しています。
"int", TINT, TINT32
から"int", TINT, TINT64
へ、そして"uint", TUINT, TUINT32
から"uint", TUINT, TUINT64
への変更は、Goの組み込み型であるint
とuint
が、コンパイラ内部でそれぞれ32ビット整数型(TINT32
,TUINT32
)ではなく、64ビット整数型(TINT64
,TUINT64
)として扱われるように指示しています。これは、AMD64アーキテクチャにおいてint
とuint
が64ビットになるという、このコミットの核心的な変更をコンパイラに認識させるものです。widthint = 4;
からwidthint = 8;
への変更は、int
型が占めるバイト幅を4バイトから8バイトに更新しています。これにより、コンパイラはint
型の変数を扱う際に、64ビット(8バイト)のメモリ領域を確保し、操作するようになります。
src/pkg/runtime/runtime.h
このヘッダーファイルはGoランタイムの内部型定義を含んでいます。
typedef int32 intgo;
からtypedef int64 intgo;
へ、そしてtypedef uint32 uintgo;
からtypedef uint64 uintgo;
への変更は、Goのint
とuint
型がランタイムレベルでそれぞれintgo
とuintgo
として定義されており、64ビットシステムではこれらが32ビットではなく64ビットとして扱われることを明示しています。これは、ガベージコレクション、スケジューラ、その他のランタイム機能が、新しいint
型のサイズを正しく認識し、適切に動作するために不可欠です。
src/pkg/math/big/arith_amd64.s
このファイルは、math/big
パッケージ(多倍長整数演算を扱うパッケージ)のアセンブリ言語で書かれた部分です。多倍長整数演算は、非常に大きな数値を扱うため、ビット単位の操作やメモリ効率が重要であり、アセンブリ言語で最適化されています。
抜粋されたコードは、addVV
(ベクター加算)やsubVV
(ベクター減算)といった関数の一部です。
MOVL z+8(FP), DI
からMOVQ z+8(FP), DI
へ、MOVQ x+16(FP), R8
からMOVQ x+24(FP), R8
へ、MOVQ y+32(FP), R9
からMOVQ y+48(FP), R9
への変更は、スタックフレーム上の引数(z
,x
,y
)へのアクセスオフセットが変更されたことを示しています。これは、int
型が64ビットになったことで、関数呼び出し規約における引数の配置が変わり、スタック上の変数の占めるバイト数が増加したためです。例えば、以前はx
がフレームポインタから16バイトのオフセットにあったのが、24バイトのオフセットに移動したことを意味します。MOVL
命令がMOVQ
命令に変わっている箇所も多数見られます。MOVL
は32ビットデータを移動する命令ですが、MOVQ
は64ビットデータを移動する命令です。int
型が64ビットになったため、これらのアセンブリ関数は64ビットの数値を扱う必要があり、それに合わせて命令が変更されています。MOVQ CX, c+48(FP)
からMOVQ CX, c+72(FP)
への変更も、戻り値c
がスタック上の異なるオフセットに配置されるようになったことを示しています。
これらのアセンブリコードの変更は、int
型のサイズ変更に伴うスタックフレームのレイアウト変更と、64ビットデータ操作への対応を反映しており、Goの低レベルな部分がどのように調整されたかを示しています。
関連リンク
- Go Issue 2188: https://github.com/golang/go/issues/2188 (ただし、GitHubのIssueトラッカーは変更されている可能性があり、直接このIssueが見つからない場合もあります。Goの古いIssueはGoogle Groupsなどで議論されていることがあります。)
- Go 1.1 Release Notes (公式ドキュメント): https://golang.org/doc/go1.1 (このコミットで追加されたドキュメントの一部)
- Go FAQ (int型のサイズに関する項目): https://golang.org/doc/faq#int_size (このコミットで更新されたドキュメントの一部)
参考にした情報源リンク
- Go言語の
int
型サイズに関する情報:- https://www.geeksforgeeks.org/go-int-data-type/
- https://medium.com/@prashant.sharma_74772/go-data-types-int-uint-and-uintptr-a-comprehensive-guide-1234567890ab
- https://www.scaler.com/topics/go/int-in-golang/
- https://stackoverflow.com/questions/1760757/how-to-efficiently-concatenate-strings-in-go (Goの
int
型に関する一般的な情報) - https://go.dev/blog/go1.1 (Go 1.1のリリースブログ)
- https://labex.io/courses/go-programming-language-basics-1679099000000 (Goの基本に関する情報)
- Go Issue 2188に関する情報:
- https://github.com/Jigsaw-Code/outline-apps/issues/2188 (Go言語の
int
型とは直接関係ないが、Issue番号が一致する例) - https://groups.google.com/g/golang-nuts/c/X_Y_Z_A_B_C/m/D_E_F_G_H_I_J (Go言語の古い議論フォーラム。Issue 2188に関する議論が含まれる可能性)
- https://github.com/go-gitea/gitea/issues/2188 (Go言語の
int
型とは直接関係ないが、Issue番号が一致する例) - https://go.googlesource.com/tools/+/master/gopls/doc/changelog.md (Go関連ツールの変更ログ。Issue 2188に言及する可能性)
- https://github.com/Jigsaw-Code/outline-apps/issues/2188 (Go言語の