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

[インデックス 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ビットプラットフォーム上でintuintを64ビットに拡張することで、この制約を解消し、Goプログラムが64ビットシステムの能力を最大限に活用できるようにすることを目的としています。これにより、20億以上の要素を持つスライスの割り当てが可能になり、より大規模なデータ処理やメモリ管理が効率的に行えるようになります。

コミットメッセージにある「Fixes #2188」は、この変更がGoのIssue 2188を解決したことを示しています。このIssueは、int型のサイズに関する議論や、それがもたらす影響についてのものであったと推測されます。

前提知識の解説

Go言語のintおよびuint

Go言語において、intuintはプラットフォームに依存する整数型です。これは、コンパイラが動作するシステムのワードサイズ(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)、リンカ、およびアセンブリ言語で書かれた標準ライブラリのコードに広範な影響を与えます。

  1. 型定義の変更:

    • src/cmd/6g/galign.c: gcコンパイラの一部であり、型の配置とアラインメントを定義します。ここでTINTTUINTTINT32/TUINT32からTINT64/TUINT64に変更され、widthintint型の幅)が4バイトから8バイトに更新されています。
    • src/cmd/6l/l.h: 6lリンカのヘッダーファイルで、IntSizeが4から8に変更されています。これは、リンカがint型を扱う際のサイズ認識を更新します。
    • src/cmd/cgo/main.go: cgoツールにおいて、intSizeMapamd64エントリが4から8に変更されています。これにより、C言語との相互運用においてint型のサイズが正しく認識されるようになります。
    • src/cmd/dist/goc2c.c: use64bitintフラグが0から1に変更されています。これは、Goのビルドシステムが64ビットintを使用することを指示します。
    • src/pkg/runtime/runtime.h: Goランタイムのヘッダーファイルで、intgouintgo(Goのintuintに対応する内部型)の定義がint32/uint32からint64/uint64に変更されています。これは、ランタイムレベルでint型のサイズが64ビットとして扱われることを保証します。
  2. アセンブリコードの変更: int型のサイズが変更されたことで、アセンブリ言語で書かれた関数におけるスタックフレームのレイアウトと、レジスタ操作の命令が影響を受けます。

    • スタックオフセットの調整: 関数引数や戻り値がスタックに配置される際のアドレスオフセットが変更されます。例えば、s+8(FP)s+24(FP)に、r+24(FP)r+32(FP)に変更されるなど、多くのオフセットが調整されています。これは、int型が32ビットから64ビットになったことで、スタック上の変数の占めるバイト数が増加したためです。
    • 命令の変更: 32ビット操作を行う命令(例: MOVLCMPLDECLADDLSUBL)が、対応する64ビット操作を行う命令(例: MOVQCMPQDECQADDQSUBQ)に変更されています。これは、int型が64ビットになったため、レジスタやメモリとの間で64ビットデータを扱う必要があるためです。
      • src/pkg/bytes/asm_amd64.s: IndexByteEqualといったバイト操作関数で、引数のオフセットや比較命令が変更されています。
      • 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への変更が顕著です。
  3. ドキュメントの更新:

    • doc/go1.1.html: Go 1.1のリリースノートに、int型のサイズ変更に関するセクションが追加されています。このセクションでは、変更の概要、ほとんどのプログラムへの影響のなさ、そして32ビットの仮定に依存するコードの挙動の変化(例: int(uint32(0))の挙動)について説明されています。
    • doc/go_faq.html: FAQドキュメントのint型のサイズに関する項目が更新され、Go 1.1以降は64ビットシステムで64ビットになることが明記されています。
  4. テストの更新:

    • test/index.go: テストスイートに、i64bigi64biggerといったテストケースが追加または修正されています。これらは、int型が64ビットになったことで、以前はコンパイルエラーになっていた大きな数値がAMD64上で正しく扱われることを検証するためのものです。

これらの変更は、Go言語のコア部分に深く関わるものであり、Goのコンパイラ、リンカ、ランタイム、標準ライブラリ、そしてドキュメントに至るまで、多岐にわたる調整が必要とされました。アセンブリコードのオフセット変更はasmlintというツールで機械的に変換され、命令の変更は手動で行われたとコミットメッセージに記載されており、この作業の複雑さを示しています。

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

このコミットにおけるコアとなるコードの変更は、主に以下のファイルに集中しています。

  1. 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;
    
  2. 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;
    
  3. 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の組み込み型であるintuintが、コンパイラ内部でそれぞれ32ビット整数型(TINT32, TUINT32)ではなく、64ビット整数型(TINT64, TUINT64)として扱われるように指示しています。これは、AMD64アーキテクチャにおいてintuintが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のintuint型がランタイムレベルでそれぞれintgouintgoとして定義されており、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 (このコミットで更新されたドキュメントの一部)

参考にした情報源リンク