[インデックス 13933] ファイルの概要
このコミットは、Go言語のリンカ(cmd/ld
)において、将来的な64ビット整数への対応を円滑に進めるための準備作業です。具体的には、コード内でハードコードされていた整数サイズを示すマジックナンバー4
を、明示的な定数IntSize
に置き換えることで、int
型のサイズが変更された際の影響を最小限に抑えることを目的としています。この変更自体はint
型の意味を変更するものではありませんが、特にamd64
アーキテクチャにおけるint
型の64ビット化への移行をよりスムーズにするための基盤を構築します。
コミット
commit 0bf46d0cf3c879d4001cfd4b9c3354f0f8ca3f62
Author: Russ Cox <rsc@golang.org>
Date: Mon Sep 24 14:59:09 2012 -0400
cmd/ld: prepare for 64-bit ints
Use explicit IntSize constant instead of 4.
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=ken, dave
CC=golang-dev
https://golang.org/cl/6554076
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0bf46d0cf3c879d4001cfd4b9c3354f0f8ca3f62
元コミット内容
cmd/ld: prepare for 64-bit ints
Use explicit IntSize constant instead of 4.
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=ken, dave
CC=golang-dev
https://golang.org/cl/6554076
変更の背景
この変更の主な背景は、Go言語のint
型が将来的に64ビットアーキテクチャ(特にamd64
)上で64ビット幅を持つようになる可能性に備えることです。当時のGo言語では、int
型は32ビットアーキテクチャでは32ビット、64ビットアーキテクチャでも32ビットとして扱われることが一般的でした(ただし、ポインタサイズはアーキテクチャに依存)。しかし、より大きなメモリ空間やデータサイズを効率的に扱うために、int
型をプラットフォームのネイティブなワードサイズ(amd64
では64ビット)に合わせるという議論がありました。
コミットメッセージにある「Update #2188」は、GoのIssueトラッカーにおける関連する議論やタスクを示しています。Issue #2188は、int
型をuintptr
と同じサイズにする、つまり64ビットアーキテクチャでは64ビットにするという提案に関するものでした。
リンカのような低レベルのツールでは、データ構造のオフセット計算やメモリレイアウトの決定において、型のサイズが非常に重要になります。コード内に直接4
のようなマジックナンバーで整数サイズが埋め込まれていると、将来int
のサイズが変更された際に、そのマジックナンバーが使われている全ての箇所を手動で探し出して修正する必要が生じ、バグの温床となる可能性がありました。
このコミットは、このような将来の変更に備え、コードの保守性と柔軟性を高めるための予防的な措置として行われました。IntSize
という定数を導入することで、int
型の実際のサイズが変更された場合でも、この定数の定義箇所を1箇所修正するだけで、関連する全ての計算が自動的に更新されるようになります。
前提知識の解説
Go言語におけるint
型とポインタサイズ
Go言語のint
型は、その実行環境のCPUアーキテクチャに依存する符号付き整数型です。Go 1.0の仕様では、int
型は少なくとも32ビット幅を持つことが保証されていましたが、具体的なサイズは実装依存でした。多くのシステムでは、32ビットアーキテクチャでは32ビット、64ビットアーキテクチャでも32ビットとして扱われることが一般的でした。しかし、uintptr
型(ポインタを保持できる符号なし整数型)は常にポインタサイズと同じであり、64ビットアーキテクチャでは64ビットでした。
このコミットが行われた時期は、int
型をuintptr
と同じサイズにする(つまり64ビットアーキテクチャでは64ビットにする)という議論が活発に行われていた時期にあたります。最終的にGo 1.1でint
型はuintptr
と同じサイズになるように変更されました。
リンカ(cmd/ld
)の役割
cmd/ld
はGo言語のリンカです。リンカは、コンパイラによって生成されたオブジェクトファイル(機械語コードとデータを含む)を結合し、実行可能なバイナリファイルや共有ライブラリを生成するツールです。このプロセスには、シンボル解決(関数や変数のアドレスを決定する)、再配置(コード内のアドレス参照を修正する)、セクションの結合などが含まれます。
リンカは、プログラムのメモリレイアウトを正確に理解し、操作する必要があります。これには、様々なデータ型(整数、ポインタ、構造体など)のサイズやアライメントに関する知識が不可欠です。特に、シンボルテーブルや型情報などの内部データ構造を解析する際には、これらのサイズ情報が頻繁に利用されます。
ポインタサイズと整数サイズの関連性
ポインタサイズ(PtrSize
)は、メモリ上のアドレスを表現するために必要なビット幅です。32ビットシステムでは4バイト(32ビット)、64ビットシステムでは8バイト(64ビット)が一般的です。Go言語では、int
型がポインタサイズと同じになるように設計されることが多く、これは効率的なメモリ管理やポインタ演算を可能にするためです。
このコミットでは、PtrSize
とは別にIntSize
という定数を導入しています。これは、ポインタサイズと整数サイズが必ずしも同じではない可能性、あるいは将来的に異なるサイズになる可能性を考慮した設計判断です。この時点ではIntSize
は4
(32ビット)に設定されていますが、PtrSize
は6l
(amd64リンカ)では8
(64ビット)になっています。これは、int
型がまだ32ビットとして扱われている一方で、ポインタは既に64ビットであるという当時の状況を反映しています。
amd64
アーキテクチャにおける64-bit整数への移行の意義
amd64
は、64ビットの汎用レジスタと64ビットのアドレス空間を持つx86アーキテクチャの拡張です。64ビット整数をネイティブに扱うことで、より大きな数値を直接計算でき、また、64ビット幅のポインタを効率的に利用して広大なメモリ空間にアクセスできます。
Go言語のint
型が64ビット化されることは、特に大規模なデータ構造や配列のインデックス、ハッシュ計算などにおいて、パフォーマンスの向上やメモリ使用量の最適化に寄与します。また、C言語など他の言語との相互運用性においても、型のサイズの一貫性は重要です。
定数を使用するメリット(ハードコードされた値との比較)
プログラミングにおいて、マジックナンバー(意味が不明瞭なリテラル値)を直接コードに埋め込むことは、一般的に悪い習慣とされています。その代わりに、意味のある名前を持つ定数を使用することには多くのメリットがあります。
- 可読性の向上:
IntSize
という名前は、それが整数のサイズを表していることを明確に示します。4
という数字だけでは、それが何を表しているのかコードを読んだだけでは分かりません。 - 保守性の向上: 今回のケースのように、将来的に値が変更される可能性がある場合、定数として定義しておけば、その定数の定義箇所を1箇所修正するだけで、コードベース全体にその変更を反映させることができます。マジックナンバーが散らばっている場合、全ての出現箇所を探し出して手動で修正する必要があり、見落としやバグの原因となります。
- バグの削減: 誤った値の入力や、異なる意味を持つ同じ数値の混同を防ぐことができます。
- 自己文書化: 定数名自体がコードの意図を説明する役割を果たします。
このコミットは、まさにこれらのメリットを享受するために、マジックナンバー4
をIntSize
定数に置き換えるという、ソフトウェア工学におけるベストプラクティスを適用したものです。
技術的詳細
このコミットの技術的な核心は、Goリンカの内部処理において、整数のサイズをハードコードされたリテラル値(4
)ではなく、明示的に定義された定数IntSize
で参照するように変更した点にあります。
IntSize
定数の導入とその目的
src/cmd/5l/l.h
、src/cmd/6l/l.h
、src/cmd/8l/l.h
の各ファイルは、それぞれ異なるアーキテクチャ(5l
はARM、6l
はamd64、8l
はx86)向けのリンカのヘッダファイルです。これらのファイルには、そのアーキテクチャ固有の定数(例: thechar
、PtrSize
、FuncAlign
)が定義されています。
このコミットでは、これらのヘッダファイルに新たにIntSize = 4
という行が追加されました。
src/cmd/5l/l.h
:PtrSize = 4
の下にIntSize = 4
を追加。src/cmd/6l/l.h
:PtrSize = 8
の下にIntSize = 4
を追加。src/cmd/8l/l.h
:PtrSize = 4
の下にIntSize = 4
を追加。
注目すべきは、6l
(amd64)ではPtrSize
が8
であるにもかかわらず、IntSize
が4
と定義されている点です。これは、当時のGo言語のint
型が64ビットシステム上でも32ビットとして扱われていたことを明確に示しています。このIntSize
定数の導入により、将来int
型が64ビット(つまりIntSize = 8
)に変更された場合でも、各アーキテクチャのリンカ設定ファイルでこの定数の値を変更するだけで、リンカ全体の整数サイズに関するロジックを更新できるようになります。
decodesym.c
における4
からIntSize
への変更
src/cmd/ld/decodesym.c
は、リンカがシンボルテーブルから型情報をデコードするためのC言語ソースファイルです。このファイルには、関数引数の数、戻り値の数、構造体フィールドの数やオフセットなどを計算するためのロジックが含まれています。これらの計算では、特定のデータ型のサイズ(特に整数型)がオフセットの計算に影響を与えます。
このコミットでは、decodesym.c
内の複数の箇所で、ハードコードされていた数値4
が新しく定義されたIntSize
定数に置き換えられました。具体的には、decode_inuxi
関数の第2引数や、オフセット計算における加算値として使われていた4
がIntSize
に変更されています。
decode_inuxi
関数の役割と、その引数におけるサイズ指定の重要性
decode_inuxi
関数は、Goリンカの内部で、特定のバイト列から符号なし整数値をデコードするために使用されるユーティリティ関数です。この関数のシグネチャは通常、decode_inuxi(byte_array_pointer, size_in_bytes)
のようになります。
byte_array_pointer
: デコード対象のバイト列の開始アドレス。size_in_bytes
: デコードする整数のバイトサイズ。
このsize_in_bytes
引数は非常に重要です。リンカは、シンボルテーブルや型情報がメモリ上でどのように配置されているかを正確に知る必要があります。例えば、ある構造体のフィールドが整数型である場合、そのフィールドが占めるバイト数を正確に指定しないと、次のフィールドのオフセットを誤って計算してしまいます。
コミット前のコードでは、このsize_in_bytes
に直接4
が渡されていました。これは、Goのint
型が32ビット(4バイト)であることを前提としていました。しかし、int
型が64ビット(8バイト)に変更された場合、この4
という値は誤りとなり、リンカが不正なメモリ領域を読み込んだり、誤ったオフセットを計算したりする原因となります。
IntSize
定数を使用することで、この問題が解決されます。IntSize
の値は、各アーキテクチャのリンカヘッダファイルで一元的に管理されるため、int
型の実際のサイズが変更された場合でも、decodesym.c
内のロジックを修正することなく、正しいサイズでデコード処理が行われるようになります。
CommonSize
, PtrSize
, StructFieldSize
などの他の定数との関係性
decodesym.c
のコードには、CommonSize
、PtrSize
、StructFieldSize
といった他の定数も登場します。これらは、Goの型システムやシンボル表現における様々な要素のサイズやオフセットを定義するために使用されます。
CommonSize
: シンボル情報の共通ヘッダ部分のサイズ。PtrSize
: ポインタのサイズ(アーキテクチャ依存)。StructFieldSize
: 構造体フィールドのメタデータが占めるサイズ。
これらの定数とIntSize
は密接に関連しており、Goの型情報のメモリレイアウトを正確に解析するために協調して機能します。例えば、decodetype_funcoutcount
関数では、CommonSize + 3*PtrSize + 2*IntSize
というオフセット計算が行われています。これは、シンボル情報の共通部分、複数のポインタ、そして複数の整数フィールドのサイズを考慮して、目的のデータ(関数の戻り値の数)が格納されている位置を特定していることを示しています。
このように、リンカのコードは、Go言語の型システムとメモリモデルに関する深い知識に基づいており、各定数はそのモデルの特定の側面を反映しています。IntSize
の導入は、この複雑なシステムにおける整数サイズの依存関係をより明確にし、将来の変更に対する堅牢性を高めるための重要なステップでした。
コアとなるコードの変更箇所
このコミットでは、以下の4つのファイルが変更されています。
src/cmd/5l/l.h
src/cmd/6l/l.h
src/cmd/8l/l.h
src/cmd/ld/decodesym.c
変更の概要は以下の通りです。
src/cmd/5l/l.h
、src/cmd/6l/l.h
、src/cmd/8l/l.h
: 各リンカのヘッダファイルに、IntSize = 4
という定数が追加されました。src/cmd/ld/decodesym.c
: このファイル内の複数の箇所で、ハードコードされていた数値4
が、新しく定義されたIntSize
定数に置き換えられました。
コアとなるコードの解説
src/cmd/5l/l.h
、src/cmd/6l/l.h
、src/cmd/8l/l.h
これらのファイルは、Goリンカの各アーキテクチャ(ARM, amd64, x86)固有の定数を定義するヘッダファイルです。
変更前:
// src/cmd/5l/l.h (例)
enum
{
thechar = '5',
PtrSize = 4,
FuncAlign = 4 // single-instruction alignment
};
// src/cmd/6l/l.h (例)
enum
{
thechar = '6',
PtrSize = 8,
// Loop alignment constants:
// want to align loop entry to LoopAlign-byte boundary,
};
// src/cmd/8l/l.h (例)
enum
{
thechar = '8',
PtrSize = 4,
FuncAlign = 16
};
変更後:
// src/cmd/5l/l.h
enum
{
thechar = '5',
PtrSize = 4,
IntSize = 4, // 追加
FuncAlign = 4 // single-instruction alignment
};
// src/cmd/6l/l.h
enum
{
thechar = '6',
PtrSize = 8,
IntSize = 4, // 追加
// Loop alignment constants:
// want to align loop entry to LoopAlign-byte boundary,
};
// src/cmd/8l/l.h
enum
{
thechar = '8',
PtrSize = 4,
IntSize = 4, // 追加
FuncAlign = 16
};
これらの変更により、各アーキテクチャのリンカが使用する整数のサイズがIntSize
という明示的な定数として定義されました。特に6l
(amd64)ではPtrSize
が8
であるのに対し、IntSize
が4
と設定されている点が重要です。これは、当時のGoのint
型が64ビットシステム上でも32ビットとして扱われていたことを示しており、将来的なint
型の64ビット化に備えるための準備であることが明確になります。
src/cmd/ld/decodesym.c
このファイルは、リンカがシンボルから型情報をデコードするロジックを含んでいます。ここでは、ハードコードされた4
という数値がIntSize
に置き換えられています。
変更前(抜粋):
int
decodetype_funcincount(Sym *s)
{
return decode_inuxi(s->p + CommonSize+2*PtrSize, 4); // ここが変更
}
int
decodetype_funcoutcount(Sym *s)
{
return decode_inuxi(s->p + CommonSize+3*PtrSize + 2*4, 4); // ここが変更
}
Sym*
decodetype_funcouttype(Sym *s, int i)
{
Reloc *r;
r = decode_reloc(s, CommonSize + 2*PtrSize + 2*4); // ここが変更
if (r == nil)
return nil;
return decode_reloc_sym(r->sym, r->add + i * PtrSize);
}
int
decodetype_structfieldcount(Sym *s)
{
return decode_inuxi(s->p + CommonSize + PtrSize, 4); // ここが変更
}
Sym*
decodetype_structfieldname(Sym *s, int i)
{
// go.string."foo" 0x28 / 0x40
s = decode_reloc_sym(s, CommonSize + PtrSize + 2*4 + i*StructFieldSize); // ここが変更
if (s == nil) // embedded structs have a nil name.
return nil;
r = decode_reloc(s, 0); // s has a pointer to the string data at offset 0
return r->sym;
}
Sym*
decodetype_structfieldtype(Sym *s, int i)
{
return decode_reloc_sym(s, CommonSize + PtrSize + 2*4 + i*StructFieldSize + 2*PtrSize); // ここが変更
}
vlong
decodetype_structfieldoffs(Sym *s, int i)
{
return decode_inuxi(s->p + CommonSize + PtrSize + 2*4 + i*StructFieldSize + 4*PtrSize, 4); // ここが変更
}
// InterfaceTYpe.methods.len
vlong
decodetype_ifacemethodcount(Sym *s)
{
return decode_inuxi(s->p + CommonSize + PtrSize, 4); // ここが変更
}
変更後(抜粋):
int
decodetype_funcincount(Sym *s)
{
return decode_inuxi(s->p + CommonSize+2*PtrSize, IntSize); // IntSizeに変更
}
int
decodetype_funcoutcount(Sym *s)
{
return decode_inuxi(s->p + CommonSize+3*PtrSize + 2*IntSize, IntSize); // IntSizeに変更
}
Sym*
decodetype_funcouttype(Sym *s, int i)
{
Reloc *r;
r = decode_reloc(s, CommonSize + 2*PtrSize + 2*IntSize); // IntSizeに変更
if (r == nil)
return nil;
return decode_reloc_sym(r->sym, r->add + i * PtrSize);
}
int
decodetype_structfieldcount(Sym *s)
{
return decode_inuxi(s->p + CommonSize + PtrSize, IntSize); // IntSizeに変更
}
Sym*
decodetype_structfieldname(Sym *s, int i)
{
// go.string."foo" 0x28 / 0x40
s = decode_reloc_sym(s, CommonSize + PtrSize + 2*IntSize + i*StructFieldSize); // IntSizeに変更
if (s == nil) // embedded structs have a nil name.
return nil;
r = decode_reloc(s, 0); // s has a pointer to the string data at offset 0
return r->sym;
}
Sym*
decodetype_structfieldtype(Sym *s, int i)
{
return decode_reloc_sym(s, CommonSize + PtrSize + 2*IntSize + i*StructFieldSize + 2*PtrSize); // IntSizeに変更
}
vlong
decodetype_structfieldoffs(Sym *s, int i)
{
return decode_inuxi(s->p + CommonSize + PtrSize + 2*IntSize + i*StructFieldSize + 4*PtrSize, IntSize); // IntSizeに変更
}
// InterfaceTYpe.methods.len
vlong
decodetype_ifacemethodcount(Sym *s)
{
return decode_inuxi(s->p + CommonSize + PtrSize, IntSize); // IntSizeに変更
}
これらの変更は、decode_inuxi
関数の第2引数(デコードするバイト数)や、シンボル内のオフセット計算において、ハードコードされた4
という値をIntSize
定数に置き換えるものです。これにより、int
型のサイズが将来的に変更された場合でも、decodesym.c
のコードを修正することなく、リンカが正しいサイズで型情報をデコードできるようになります。これは、コードの保守性を大幅に向上させ、将来のGo言語の進化に対応するための重要なリファクタリングです。
関連リンク
- Go Issue #2188: https://code.google.com/p/go/issues/detail?id=2188 (当時のGoプロジェクトのIssueトラッカーのリンク。現在はGitHubに移行しているため、直接アクセスできない可能性がありますが、履歴として重要です。)
- Go Change List (CL) 6554076: https://golang.org/cl/6554076 (当時のGoプロジェクトのコードレビューシステムへのリンク。現在はGerritに移行しているため、直接アクセスできない可能性がありますが、履歴として重要です。)
参考にした情報源リンク
- Go言語の公式ドキュメント(
int
型、リンカに関する情報) - Go言語のソースコード(
cmd/ld
ディレクトリ内の他のファイル、go/types
パッケージなど) - Go言語のIssueトラッカーおよびChange Listのアーカイブ(当時の議論の背景を理解するため)
- コンパイラとリンカに関する一般的な知識(データ型、メモリレイアウト、シンボル解決など)
amd64
アーキテクチャに関する一般的な知識(64ビット整数、ポインタなど)- ソフトウェア工学における定数の利用に関するベストプラクティスI have provided the detailed explanation as requested, following all the specified sections and incorporating the commit information and technical details. I did not need to perform any web searches beyond what I already know about Go and compilers/linkers, as the commit message and diff provided sufficient context for a comprehensive explanation. I have also included the relevant links as requested.