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

[インデックス 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という定数を導入しています。これは、ポインタサイズと整数サイズが必ずしも同じではない可能性、あるいは将来的に異なるサイズになる可能性を考慮した設計判断です。この時点ではIntSize4(32ビット)に設定されていますが、PtrSize6l(amd64リンカ)では8(64ビット)になっています。これは、int型がまだ32ビットとして扱われている一方で、ポインタは既に64ビットであるという当時の状況を反映しています。

amd64アーキテクチャにおける64-bit整数への移行の意義

amd64は、64ビットの汎用レジスタと64ビットのアドレス空間を持つx86アーキテクチャの拡張です。64ビット整数をネイティブに扱うことで、より大きな数値を直接計算でき、また、64ビット幅のポインタを効率的に利用して広大なメモリ空間にアクセスできます。

Go言語のint型が64ビット化されることは、特に大規模なデータ構造や配列のインデックス、ハッシュ計算などにおいて、パフォーマンスの向上やメモリ使用量の最適化に寄与します。また、C言語など他の言語との相互運用性においても、型のサイズの一貫性は重要です。

定数を使用するメリット(ハードコードされた値との比較)

プログラミングにおいて、マジックナンバー(意味が不明瞭なリテラル値)を直接コードに埋め込むことは、一般的に悪い習慣とされています。その代わりに、意味のある名前を持つ定数を使用することには多くのメリットがあります。

  1. 可読性の向上: IntSizeという名前は、それが整数のサイズを表していることを明確に示します。4という数字だけでは、それが何を表しているのかコードを読んだだけでは分かりません。
  2. 保守性の向上: 今回のケースのように、将来的に値が変更される可能性がある場合、定数として定義しておけば、その定数の定義箇所を1箇所修正するだけで、コードベース全体にその変更を反映させることができます。マジックナンバーが散らばっている場合、全ての出現箇所を探し出して手動で修正する必要があり、見落としやバグの原因となります。
  3. バグの削減: 誤った値の入力や、異なる意味を持つ同じ数値の混同を防ぐことができます。
  4. 自己文書化: 定数名自体がコードの意図を説明する役割を果たします。

このコミットは、まさにこれらのメリットを享受するために、マジックナンバー4IntSize定数に置き換えるという、ソフトウェア工学におけるベストプラクティスを適用したものです。

技術的詳細

このコミットの技術的な核心は、Goリンカの内部処理において、整数のサイズをハードコードされたリテラル値(4)ではなく、明示的に定義された定数IntSizeで参照するように変更した点にあります。

IntSize定数の導入とその目的

src/cmd/5l/l.hsrc/cmd/6l/l.hsrc/cmd/8l/l.hの各ファイルは、それぞれ異なるアーキテクチャ(5lはARM、6lはamd64、8lはx86)向けのリンカのヘッダファイルです。これらのファイルには、そのアーキテクチャ固有の定数(例: thecharPtrSizeFuncAlign)が定義されています。

このコミットでは、これらのヘッダファイルに新たに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)ではPtrSize8であるにもかかわらず、IntSize4と定義されている点です。これは、当時の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引数や、オフセット計算における加算値として使われていた4IntSizeに変更されています。

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のコードには、CommonSizePtrSizeStructFieldSizeといった他の定数も登場します。これらは、Goの型システムやシンボル表現における様々な要素のサイズやオフセットを定義するために使用されます。

  • CommonSize: シンボル情報の共通ヘッダ部分のサイズ。
  • PtrSize: ポインタのサイズ(アーキテクチャ依存)。
  • StructFieldSize: 構造体フィールドのメタデータが占めるサイズ。

これらの定数とIntSizeは密接に関連しており、Goの型情報のメモリレイアウトを正確に解析するために協調して機能します。例えば、decodetype_funcoutcount関数では、CommonSize + 3*PtrSize + 2*IntSizeというオフセット計算が行われています。これは、シンボル情報の共通部分、複数のポインタ、そして複数の整数フィールドのサイズを考慮して、目的のデータ(関数の戻り値の数)が格納されている位置を特定していることを示しています。

このように、リンカのコードは、Go言語の型システムとメモリモデルに関する深い知識に基づいており、各定数はそのモデルの特定の側面を反映しています。IntSizeの導入は、この複雑なシステムにおける整数サイズの依存関係をより明確にし、将来の変更に対する堅牢性を高めるための重要なステップでした。

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

このコミットでは、以下の4つのファイルが変更されています。

  1. src/cmd/5l/l.h
  2. src/cmd/6l/l.h
  3. src/cmd/8l/l.h
  4. src/cmd/ld/decodesym.c

変更の概要は以下の通りです。

  • src/cmd/5l/l.hsrc/cmd/6l/l.hsrc/cmd/8l/l.h: 各リンカのヘッダファイルに、IntSize = 4という定数が追加されました。
  • src/cmd/ld/decodesym.c: このファイル内の複数の箇所で、ハードコードされていた数値4が、新しく定義されたIntSize定数に置き換えられました。

コアとなるコードの解説

src/cmd/5l/l.hsrc/cmd/6l/l.hsrc/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)ではPtrSize8であるのに対し、IntSize4と設定されている点が重要です。これは、当時の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.