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

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

このコミットは、Goランタイムが64ビット整数型をより適切に扱えるようにするための準備作業です。具体的には、マップ、スライス、文字列のlencapの型がint32ではなくintであることをランタイムに認識させ、intint32の関数引数および結果の区別を明確にしています。これにより、将来的なint型の64ビット化(特にamd64アーキテクチャにおいて)がスムーズに行われるように基盤を整えています。

コミット

commit 0b08c9483f5f447083616b7b5e6ddf04edffc379
Author: Russ Cox <rsc@golang.org>
Date:   Mon Sep 24 14:58:34 2012 -0400

    runtime: prepare for 64-bit ints
    
    This CL makes the runtime understand that the type of
    the len or cap of a map, slice, or string is 'int', not 'int32',
    and it is also careful to distinguish between function arguments
    and results of type 'int' vs type 'int32'.
    
    In the runtime, the new typedefs 'intgo' and 'uintgo' refer
    to Go int and uint. The C types int and uint continue to be
    unavailable (cause intentional compile errors).
    
    This CL does not change the meaning of int, but it should make
    the eventual change of the meaning of int on amd64 a bit
    smoother.
    
    Update #2188.
    
    R=iant, r, dave, remyoudompheng
    CC=golang-dev
    https://golang.org/cl/6551067

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

https://github.com/golang/go/commit/0b08c9483f5f447083616b7b5e6ddf04edffc379

元コミット内容

runtime: prepare for 64-bit ints

This CL makes the runtime understand that the type of
the len or cap of a map, slice, or string is 'int', not 'int32',
and it is also careful to distinguish between function arguments
and results of type 'int' vs type 'int32'.

In the runtime, the new typedefs 'intgo' and 'uintgo' refer
to Go int and uint. The C types int and uint continue to be
unavailable (cause intentional compile errors).

This CL does not change the meaning of int, but it should make
the eventual change of the meaning of int on amd64 a bit
smoother.

Update #2188.

R=iant, r, dave, remyoudompheng
CC=golang-dev
https://golang.org/cl/6551067

変更の背景

この変更の主な背景は、Go言語のint型が将来的に64ビットアーキテクチャ(特にamd64)上で64ビット幅を持つようになることへの準備です。Go言語の設計思想として、int型はプラットフォームのネイティブなワードサイズに合わせるというものがあります。しかし、当時のGoのランタイム内部では、int型が常に32ビットであるという前提でコードが書かれている箇所が多く存在していました。

特に、マップ、スライス、文字列の長さ(len)や容量(cap)を扱う際に、これらの値がint32として扱われていることが問題でした。64ビットシステムでintが64ビットになった場合、これらの操作が正しく機能しなくなる可能性があります。

このコミットは、Goのint型が64ビットになるという将来の変更に備え、ランタイムコードの互換性を確保することを目的としています。コミットメッセージに記載されているUpdate #2188は、この64ビットintへの移行に関連するGoのIssueを参照していると考えられます。Web検索の結果からも、Goのint型がx86_64システムで64ビットになる可能性についての議論が示唆されています。

前提知識の解説

  • Go言語のint: Go言語の組み込み型であるintは、その実行環境のCPUアーキテクチャに依存してサイズが決定されます。32ビットシステムでは32ビット幅、64ビットシステムでは64ビット幅を持ちます。これはC言語のint型と同様の特性ですが、Goではより厳密に型が扱われます。
  • int32: int32は、Go言語において常に32ビット幅を持つ符号付き整数型です。int型とは異なり、プラットフォームに依存せず固定のサイズを持ちます。
  • ランタイム (Runtime): Go言語のランタイムは、Goプログラムの実行を管理する低レベルのシステムです。ガベージコレクション、スケジューリング、チャネル通信、メモリ管理など、Go言語の多くの機能がランタイムによって提供されます。ランタイムのコードは、Go言語自体で書かれている部分と、C言語やアセンブリ言語で書かれている部分があります。
  • lencap:
    • len(): スライス、マップ、チャネル、文字列の要素数を返します。
    • cap(): スライスやチャネルの容量(割り当てられたメモリの最大要素数)を返します。 これらはGo言語の組み込み関数であり、その戻り値の型はintです。
  • amd64アーキテクチャ: x86-64とも呼ばれる64ビットのCPUアーキテクチャです。現代のほとんどのデスクトップPCやサーバーで採用されています。このアーキテクチャでは、ポインタやレジスタが64ビット幅を持つため、int型も64ビットにすることでパフォーマンスの向上が期待できます。
  • typedef (C言語): C言語におけるtypedefは、既存のデータ型に新しい名前を付けるために使用されます。これにより、コードの可読性を向上させたり、プラットフォーム間の互換性を高めたりすることができます。

技術的詳細

このコミットの主要な技術的変更点は以下の通りです。

  1. intgouintgoの導入:

    • GoランタイムのC言語部分において、Go言語のintおよびuint型に対応する新しいtypedefとしてintgouintgoが導入されました。
    • これにより、C言語のintuintと、Go言語のintuintを明確に区別できるようになります。C言語のintuintは意図的にコンパイルエラーを引き起こすように設定され、Goの型を使用するように強制されます。
    • src/pkg/runtime/runtime.hで、_64BITが定義されているかどうかに応じてintgouintgoの基底型がint32/uint32に設定されています。これは、この時点ではintのサイズは変更されていないが、将来的に64ビットになることを想定しているためです。
  2. lenおよびcapの型変更:

    • マップ、スライス、文字列のlencapを扱うランタイム関数や構造体において、int32からintgo(またはuint32からuintgo)への型変更が行われました。
    • 例えば、src/pkg/reflect/value.goでは、chanlenmaplenの戻り値がint32からintに変更されています。これは、Goの組み込み関数lencapの戻り値がintであることに合わせるためです。
    • src/pkg/runtime/chan.csrc/pkg/runtime/hashmap.csrc/pkg/runtime/slice.cなどのファイルで、チャネル、マップ、スライスの内部構造体(例: Hchanqcount, dataqsiz, sendx, recvxHmapcountSlicelen, cap)のフィールド型がuint32からuintgoに変更されています。
  3. goc2c.cの変更:

    • src/cmd/dist/goc2c.cは、GoのソースコードをC言語のソースコードに変換するツールの一部です。
    • このファイルでは、type_tableintgouintgoが追加され、Goのintuintintgouintgoに変換されるようにロジックが変更されています。
    • 特に、read_type関数内で、intuintという文字列が検出された場合に、intgouintgoに変換する処理が追加されています。
    • また、amd64アーキテクチャでのintuintのサイズを8バイトに設定するための条件付きコンパイルフラグuse64bitintが導入されていますが、このコミット時点ではuse64bitint = 0となっており、実際のサイズ変更は行われていません。これは将来の変更のための準備です。
  4. 関数シグネチャの変更:

    • ランタイム内の多くの関数で、引数や戻り値の型がint32からintgo(またはuint32からuintgo/uintptr)に変更されています。
    • 例えば、runtime·SetCPUProfileRateの引数hzint32からintgoに、reflect·makechansizeuint32からuint64に、runtime·mallocgcrateint32からintgoに、runtime·getfinalizernretint32*からuintptr*に変更されています。

これらの変更は、Goのint型が64ビットになるという将来の変更に対して、ランタイムが正しく動作し、型の一貫性が保たれるようにするための重要なステップです。

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

このコミットにおけるコアとなるコードの変更箇所は多岐にわたりますが、特に重要なのは以下のファイル群です。

  1. src/cmd/dist/goc2c.c:

    • type_tableintgouintgoが追加され、Goのint/uintがこれらの新しい型にマッピングされるように変更。
    • read_type関数で、Goのint/uintintgo/uintgoに変換するロジックが追加。
    • amd64アーキテクチャでのintuintのサイズを8バイトにするための準備コード(use64bitintフラグ)が追加。
  2. src/pkg/reflect/value.go:

    • Value.Len()メソッド内で呼び出されるchanlenmaplenの戻り値の型がint32からintに変更。
    • MakeChan関数のbuffer引数がuint32からuint64に変更され、それに伴いmakechan関数のシグネチャも変更。
    • chancap, chanlen, maplenなどのランタイム関数の宣言がint32からintに変更。
  3. src/pkg/runtime/runtime.h:

    • intgouintgotypedefが追加され、Goのintuintに対応付けられる。32ビットと64ビット環境で異なる基底型を持つように定義。
    • StringSlice構造体のlencapフィールドの型がint32/uint32からintgo/uintgoに変更。
  4. src/pkg/runtime/chan.c, src/pkg/runtime/hashmap.c, src/pkg/runtime/slice.c:

    • これらのファイル内のチャネル、マップ、スライスの内部構造体(Hchan, Hmap, Slice)のlen, cap, countなどのフィールドの型がuint32からuintgoに変更。
    • 関連するランタイム関数の引数や戻り値の型もint32/uint32からintgo/uintgo/uintptrに変更。例えば、runtime·makechan_chint引数がint64からuintptrに、reflect·makechansizeuint32からuint64に、runtime·makeslicelencapint64からintgoに、runtime·copyretint32からintgoに変更されています。

コアとなるコードの解説

src/cmd/dist/goc2c.c

このファイルは、GoのソースコードをC言語のソースコードに変換する際に、Goの型をCの型にマッピングする役割を担っています。

// 変更前
// {"int", 4},
// {"uint", 4},

// 変更後
// {"intgo", 4},
// {"uintgo", 4},

type_tableは、Goの型名とそれに対応するC言語でのサイズを定義しています。ここでintuintintgouintgoに置き換えられ、Goのint/uintがランタイム内部で新しい型名で参照されるようになります。これにより、C言語のint/uintとの混同を避けることができます。

// 変更前
// if (*p != '*')
//     return p;

// 変更後
// if (*p != '*' && !streq(p, "int") && !streq(p, "uint"))
//     return p;

read_type関数は、Goの型名を読み取る際に、ポインタ型(*で始まる)でない場合にそのまま返す処理をしていました。この変更により、intuintという文字列も特別扱いされ、ポインタ型でなくても処理が続行されるようになります。

// 変更前
// xmemmove(q, p, len);
// while (pointer_count > 0) {
//     q[len] = '*';
//     ++len;
//     --pointer_count;
// }

// 変更後
// xmemmove(q, p, len);
// // Turn int/uint into intgo/uintgo.
// if((len == 3 && xmemcmp(q, "int", 3) == 0) || (len == 4 && xmemcmp(q, "uint", 4) == 0)) {
//     q[len++] = 'g';
//     q[len++] = 'o';
// }
// while (pointer_count-- > 0)
//     q[len++] = '*';

この部分では、Goの型名がintまたはuintである場合に、末尾にgoを追加してintgoまたはuintgoに変換する処理が追加されています。これにより、Goのint/uintがランタイム内部でintgo/uintgoとして扱われるようになります。

src/pkg/reflect/value.go

reflectパッケージは、Goプログラムが自身の構造を検査・操作するための機能を提供します。

// 変更前
// case Chan:
//     return int(chanlen(v.iword()))
// case Map:
//     return int(maplen(v.iword()))

// 変更後
// case Chan:
//     return chanlen(v.iword())
// case Map:
//     return maplen(v.iword())

Value.Len()メソッドは、チャネルやマップの長さを取得する際に、chanlenmaplenの戻り値をintにキャストしていました。この変更により、キャストが不要になり、chanlenmaplen自体がintを返すように変更されたことが示唆されます。

// 変更前
// func chancap(ch iword) int32
// func chanlen(ch iword) int32
// func maplen(m iword) int32

// 変更後
// func chancap(ch iword) int
// func chanlen(ch iword) int
// func maplen(m iword) int

ランタイム関数chancap, chanlen, maplenの宣言がint32からintに変更されています。これは、Goの組み込み関数caplenの戻り値の型であるintに合わせるためです。

src/pkg/runtime/runtime.h

このヘッダーファイルは、GoランタイムのC言語部分で共通して使用される型定義や関数宣言を含んでいます。

// 変更前
// #ifdef _64BIT
// typedef uint64 uintptr;
// typedef int64 intptr;
// #else
// typedef uint32 uintptr;
// typedef int32 intptr;
// #endif

// 変更後
// #ifdef _64BIT
// typedef uint64 uintptr;
// typedef int64 intptr;
// typedef int32 intgo; // Go's int
// typedef uint32 uintgo; // Go's uint
// #else
// typedef uint32 uintptr;
// typedef int32 intptr;
// typedef int32 intgo; // Go's int
// typedef uint32 uintgo; // Go's uint
// #endif

intgouintgotypedefが追加されています。この時点では、64ビット環境でもintgoint32にマッピングされていますが、これは将来的な変更のための準備であり、Goのintが64ビットになる際にここを変更することで対応できるようになります。

// 変更前
// struct String
// {
//     byte* str;
//     int32 len;
// };
// struct Slice
// {
//     byte* array;
//     uint32 len;
//     uint32 cap;
// };

// 変更後
// struct String
// {
//     byte* str;
//     intgo len;
// };
// struct Slice
// {
//     byte* array;
//     uintgo len;
//     uintgo cap;
// };

String構造体のlenフィールドとSlice構造体のlenおよびcapフィールドの型が、それぞれint32uint32からintgouintgoに変更されています。これにより、これらのフィールドがGoのint型と同じセマンティクスを持つようになります。

src/pkg/runtime/slice.c

スライスの操作に関するランタイムコードが含まれています。

// 変更前
// void
// runtime·makeslice(SliceType *t, int64 len, int64 cap, Slice ret)
// {
//     if(len < 0 || (int32)len != len)
//         runtime·panicstring("makeslice: len out of range");
//     if(cap < len || (int32)cap != cap || t->elem->size > 0 && cap > ((uintptr)-1) / t->elem->size)
//         runtime·panicstring("makeslice: cap out of range");
//     // ...
// }

// 変更後
// void
// runtime·makeslice(SliceType *t, int64 len, int64 cap, Slice ret)
// {
//     if(len < 0 || (intgo)len != len)
//         runtime·panicstring("makeslice: len out of range");
//     // ...
//     if(cap < len || (intgo)cap != cap || t->elem->size > 0 && cap > MaxMem / t->elem->size)
//         runtime·panicstring("makeslice: cap out of range");
//     // ...
// }

runtime·makeslice関数では、lencapの範囲チェックにおいて、int32へのキャストがintgoへのキャストに変更されています。また、capの最大値チェックにMaxMemが使用されるようになり、より汎用的なメモリ制限チェックになっています。

これらの変更は、Goのint型が64ビットになるという将来の変更に備え、ランタイムが正しく動作し、型の一貫性が保たれるようにするための重要なステップです。

関連リンク

参考にした情報源リンク

  • Go言語のint型に関するドキュメント (Go公式ドキュメント): https://go.dev/ref/spec#Numeric_types
  • Go言語のランタイムに関する情報 (Go公式ドキュメントや関連するブログ記事など)
  • C言語のtypedefに関する情報
  • Web検索: "Go issue 2188", "Go int 64-bit"