[インデックス 1393] ファイルの概要
このコミットは、Go言語の標準ライブラリの一部であるsrc/lib/bignum.go
(多倍長整数演算ライブラリ)と、そのテストファイルsrc/lib/bignum_test.go
、そしてビルド設定ファイルsrc/lib/Makefile
に対する変更を含んでいます。主な変更点は、bignum
パッケージ内の関数シグネチャと変数宣言におけるポインタ型(*Natural
)から値型(Natural
)への移行です。これは、当時のGoコンパイラ6g
のバグを回避し、新しい言語の「レジーム」(規則や慣習)に適合させるための対応として記述されています。
コミット
commit 9ccf39bd684d5d6384259cedd9ee0f567796225b
Author: Rob Pike <r@golang.org>
Date: Sat Dec 20 17:25:43 2008 -0800
update to new regime.
lines marked BUG are rewrites working around 6g bug.
R=rsc
DELTA=161 (42 added, 2 deleted, 117 changed)
OCL=21689
CL=21689
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9ccf39bd684d5d6384259cedd9ee0f567796225b
元コミット内容
update to new regime.
lines marked BUG are rewrites working around 6g bug.
R=rsc
DELTA=161 (42 added, 2 deleted, 117 changed)
OCL=21689
CL=21689
変更の背景
このコミットの背景には、Go言語の初期開発段階におけるコンパイラの挙動と、言語設計の進化があります。コミットメッセージにある「new regime」(新しい体制/規則)と「6g bug」という記述がその核心を示しています。
当時のGo言語はまだ開発途上にあり、コンパイラ(特に6g
、x86-64アーキテクチャ向けのGoコンパイラ)には、特定のコードパターン、特にポインタの扱いに関してバグが存在していたと考えられます。bignum
パッケージのような数値演算ライブラリでは、パフォーマンスとメモリ効率のためにポインタを多用することが一般的ですが、このバグが原因で予期せぬ動作やコンパイルエラーが発生していた可能性があります。
また、「new regime」という言葉は、Go言語の型システムやメモリ管理、あるいは関数呼び出し規約に関する内部的な変更を示唆しています。Go言語は、値渡しとポインタ渡しのセマンティクスを明確に区別しますが、初期の段階ではその実装や推奨されるパターンが固まっていなかった可能性があります。このコミットは、コンパイラのバグを回避しつつ、Go言語が目指すセマンティクス(おそらく値渡しをより推奨する方向性)にコードベースを適合させるための対応であったと推測されます。
具体的には、多倍長整数のような大きなデータ構造を扱う場合、通常はコピーコストを避けるためにポインタで渡すのが効率的です。しかし、コンパイラのバグや新しい言語設計の方向性により、一時的に値渡しに切り替えるか、あるいは値渡しでも効率的に動作するようにコードを調整する必要があったと考えられます。
前提知識の解説
Go言語の初期開発とコンパイラ (6g)
Go言語は2007年末に設計が始まり、2009年にオープンソースとして公開されました。このコミットが行われた2008年12月は、Go言語がまだ活発に開発されていた初期段階にあたります。
- 6g: Go言語の初期のコンパイラ群は、ターゲットアーキテクチャに基づいて命名されていました。
6g
はx86-64(64ビットIntel/AMD)アーキテクチャ向けのGoコンパイラを指します。他にも8g
(x86-32)、5g
(ARM)などがありました。これらのコンパイラは、現在のgo build
コマンドが内部的に使用するcmd/compile
とは異なり、より低レベルなツールとして機能していました。初期のGoコンパイラは、C言語で書かれたgc
(Go Compiler)というコンパイラ群の一部でした。 - コンパイラのバグ: ソフトウェア開発の初期段階では、コンパイラ自体にバグが含まれていることは珍しくありません。特に、新しい言語機能や最適化が導入される際には、予期せぬ挙動を引き起こすことがあります。このコミットで言及されている「6g bug」は、
bignum
パッケージの特定のコードパターンが6g
コンパイラで正しく処理されない問題であったと推測されます。
Go言語におけるポインタと値のセマンティクス
Go言語は、C++やJavaのようなオブジェクト指向言語とは異なるアプローチで型とメモリを扱います。
- 値型と参照型: Goには厳密な意味での「参照型」という概念はありませんが、ポインタ(
*T
)を使用することで、変数のアドレスを渡し、間接的にその値を操作することができます。スライス、マップ、チャネルなどは内部的にポインタのような振る舞いをしますが、これらは「参照型」ではなく「ヘッダにポインタを含む値型」と理解するのが適切です。 - 値渡し: Goの関数引数は常に値渡しです。これは、引数として渡された変数のコピーが関数内で使用されることを意味します。構造体のような複合型を値渡しすると、その構造体全体のコピーが作成されます。
- ポインタ渡し: 変数のアドレス(ポインタ)を値として渡すことで、関数内で元の変数を変更できます。
*T
型はポインタ型であり、そのポインタが指す先の値は*ptr
でアクセスできます。 new
キーワード: Goのnew
組み込み関数は、指定された型のゼロ値に初期化された新しい項目を割り当て、そのアドレス(ポインタ)を返します。例えば、new(Natural)
は*Natural
型のポインタを返します。&
演算子: 変数のアドレスを取得するために使用されます。例えば、&myVar
はmyVar
のアドレスを返します。
このコミットでは、*Natural
からNatural
への変更が多用されており、これはポインタ渡しから値渡しへの移行、またはポインタのデリファレンス(*
)とアドレス取得(&
)の明示的な使用を伴う変更を示しています。
多倍長整数 (bignum)
bignum
パッケージは、Go言語で任意の精度の整数(非常に大きな整数)を扱うためのライブラリです。通常のint
やint64
型では表現できない桁数の数値を扱う際に使用されます。
Digit
型:bignum
パッケージでは、多倍長整数を構成する「桁」を表すためにDigit
型が定義されています。これは通常、uint
やuint32
などの符号なし整数型に対応します。Natural
型:[]Digit
(Digitのスライス)として定義されており、符号なしの多倍長整数を表します。スライスはGoにおいて参照セマンティクスを持つ値型であるため、スライス自体を値渡ししても、その内部の配列は共有されます。しかし、スライスヘッダ(ポインタ、長さ、容量)はコピーされます。
技術的詳細
このコミットの技術的な詳細は、主にbignum.go
におけるNatural
型の扱い方の変更に集約されます。
-
*Natural
からNatural
への変更:- 多くの関数シグネチャで、引数や戻り値の型が
*bignum.Natural
からbignum.Natural
に変更されています。 - 例:
func (x *Natural) Add(y *Natural) *Natural
がfunc (x *Natural) Add(y Natural) Natural
に変更。 - これは、多倍長整数オブジェクト自体をポインタで渡すのではなく、値として渡すことを意味します。Goのスライスはヘッダが値渡しされるため、スライス自体を値渡ししても、その基盤となる配列は共有されます。しかし、関数内でスライスを再割り当て(
new
など)すると、新しい基盤配列が作成され、元のスライスとは異なるものになります。 - この変更は、
6g
コンパイラのポインタ関連のバグを回避するため、またはGo言語の新しい推奨されるコーディングスタイルに合わせるためのものです。
- 多くの関数シグネチャで、引数や戻り値の型が
-
new(*Natural, ...)
からnew(Natural, ...)
への変更:new
組み込み関数の使用方法が変更されています。- 例:
z := new(*Natural, n + 1)
がz := new(Natural, n + 1)
に変更。 new(*Natural, ...)
は*Natural
型のポインタを生成し、そのポインタが指す先はNatural
型です。これは二重ポインタのような状況を生み出す可能性があります。new(Natural, ...)
はNatural
型のポインタを生成します。- この変更は、
Natural
型のインスタンスを直接指すポインタを生成するように修正し、ポインタのポインタのような複雑さを避けることを目的としていると考えられます。
-
ポインタの明示的なデリファレンスとアドレス取得:
*x
(デリファレンス)や&t
(アドレス取得)がコードの随所に導入されています。- 例:
return y.Add(x)
がreturn y.Add(*x)
に変更。 - 例:
t, d = DivMod1(t, Digit(base))
がt, d = DivMod1(&t, Digit(base))
に変更。 - これは、関数が値を受け取るようになったため、元のポインタが指す値にアクセスするために明示的なデリファレンスが必要になったり、あるいは関数がポインタを期待する場合に値のアドレスを渡す必要が生じたためです。
- 特に、
DivMod1
やMulAdd1
のように、引数として受け取ったNatural
型の値を関数内で変更する(in-place operation)場合、値渡しではその変更が呼び出し元に反映されないため、ポインタを渡す必要があります。このため、&t
のようにアドレスを渡すように修正されています。
-
テストコードの修正:
bignum_test.go
でも、bignum.Natural
を引数として受け取るテストヘルパー関数(NAT_EQ
,Add
,Sum
,Mul
など)のシグネチャが*bignum.Natural
からbignum.Natural
に変更されています。- テスト内の
NAT_EQ
関数では、エラーメッセージのフォーマット文字列がx = %v\ny = %v
からx = %v\ny = %v
に変更され、引数もx, y
から&x, &y
に変更されています。これは、%v
フォーマット指定子がポインタを受け取った場合にその値を出力するように、あるいは値型になったx
とy
のアドレスを渡すことで、より詳細なデバッグ情報を提供するためと考えられます。 - テストケースのインデックスが変更されたり、
//BUG
コメントが追加されたりしています。これは、コンパイラのバグを回避するためのコード変更がテストの挙動に影響を与え、一時的にテストを無効化したり、新しいテストパターンを導入したりする必要があったことを示唆しています。
これらの変更は、Go言語の型システムとコンパイラの成熟に伴う、より堅牢で予測可能なコードの記述方法への移行を示しています。
コアとなるコードの変更箇所
src/lib/bignum.go
- 定数初期化:
NatZero
,NatOne
,NatTwo
,NatTen
の初期化から*&
が削除され、直接Natural{}
やNatural{1}
のように値で初期化されるようになりました。- NatZero Natural = *&Natural{}; - NatOne Natural = *&Natural{1}; - NatTwo Natural = *&Natural{2}; - NatTen Natural = *&Natural{10}; + NatZero Natural = Natural{}; + NatOne Natural = Natural{1}; + NatTwo Natural = Natural{2}; + NatTen Natural = Natural{10};
- 関数シグネチャの変更:
Normalize
,Add
,Sub
,Mul
,Unpack
,Pack
,Div
,Mod
,DivMod
,Shl
,Shr
,And
,Or
,Xor
,Cmp
,DivMod1
,MulAdd1
,NatFromString
,Pow
,MulRange
,Fact
,Binomial
,Gcd
,MakeInt
,MulNat
,MakeRat
など、多くのメソッドや関数の引数や戻り値の型が*Natural
からNatural
に変更されています。-func Normalize(x *Natural) Natural { +func Normalize(x Natural) Natural {
-func (x *Natural) Add(y *Natural) *Natural { +func (x *Natural) Add(y Natural) Natural {
new
の引数変更:new(*Natural, ...)
がnew(Natural, ...)
に変更されています。- z := new(*Natural, n + 1); + z := new(Natural, n + 1);
- ポインタのデリファレンスとアドレス取得:
y.Add(x)
がy.Add(*x)
に。Unpack(x)
がUnpack(*x)
に。Shl(z[m-n : m], x, s%W)
がShl(z[m-n : m], *x, s%W)
に。Shr(z, x[n-m : n], s%W)
がShr(z, (*x)[n-m : n], s%W)
に。Copy(z[m : n], x[m : n])
がCopy(z[m : n], (*x)[m : n])
に。DivMod1(t, Digit(base))
がDivMod1(&t, Digit(base))
に。MulAdd1(x, Digit(base), Digit(d))
がMulAdd1(&x, Digit(base), Digit(d))
に。Pow
メソッド内でx := *xp
のように、レシーバのポインタをデリファレンスして値として扱うように変更。Gcd
メソッド内でx := *xp
のように、レシーバのポインタをデリファレンスして値として扱うように変更。
//BUG
コメントの追加: コンパイラのバグを回避するためのコード変更箇所に//BUG
コメントが追加されています。これは、将来的にコンパイラのバグが修正された際に、より簡潔な元のコードに戻すためのマーカーとして機能します。
src/lib/bignum_test.go
- テストヘルパー関数のシグネチャ変更:
NatFromString
,NAT_EQ
,Add
,Sum
,Mul
などの関数が、*bignum.Natural
を引数に取る代わりにbignum.Natural
を引数に取るように変更されています。 NAT_EQ
のフォーマット文字列と引数変更:tester.Fatalf
のフォーマット文字列と引数が、値型になったx
とy
のアドレスを渡すように修正されています。- tester.Fatalf("TEST failed: %s (%d)\nx = %v\ny = %v", test_msg, n, x, y); + tester.Fatalf("TEST failed: %s (%d)\nx = %v\ny = %v", test_msg, n, &x, &y);
- テストケースの修正と
//BUG
コメント:TestNatConv
,TestRatConv
,TestNatAdd
,TestNatDiv
,TestNatShift
,TestNatGcd
,TestNatPop
などのテスト関数で、//BUG
コメントが追加され、コンパイラのバグを回避するためのコードがテスト内でも明示的に記述されています。これは、テストが元の意図通りに動作するように、一時的なワークアラウンドを適用していることを示します。
src/lib/Makefile
bignum
のコメントアウトが解除され、ビルド対象に含まれるようになりました。- -# bignum\ + bignum\
コアとなるコードの解説
このコミットのコアとなる変更は、Go言語のbignum
パッケージにおける多倍長整数Natural
型の扱い方を、ポインタベースから値ベースへとシフトさせた点にあります。
Go言語では、関数に引数を渡す際、常に値渡しが行われます。これは、引数のコピーが作成され、関数内でそのコピーが操作されることを意味します。もし関数内で元の変数を変更したい場合は、その変数のアドレス(ポインタ)を渡す必要があります。
変更前は、bignum.Natural
型のオブジェクトをポインタ(*Natural
)として関数に渡していました。これは、大きなデータ構造である多倍長整数をコピーするコストを避けるための一般的な最適化手法です。しかし、コミットメッセージにあるように、当時の6g
コンパイラにはポインタの扱いにバグがあったため、このポインタベースの設計が問題を引き起こしていました。
変更後、多くの関数シグネチャで引数や戻り値の型が*Natural
からNatural
に変更されました。これは、Natural
型([]Digit
、つまりDigit
のスライス)を値として渡すことを意味します。Goのスライスは、内部的にはポインタ、長さ、容量の3つの要素を持つ構造体です。スライスを値渡しすると、この3つの要素がコピーされますが、スライスが指す基盤となる配列自体はコピーされません。したがって、関数内でスライスの要素を変更した場合、その変更は元のスライスにも反映されます。
しかし、関数内でスライスをnew
で再割り当てしたり、新しいスライスを作成して代入したりすると、それは新しい基盤配列を指すことになり、元のスライスとは独立したものになります。このため、DivMod1
やMulAdd1
のように、関数内で引数として受け取ったNatural
型の値を「インプレース」(その場で)変更する操作を行う場合、値渡しではその変更が呼び出し元に反映されません。この問題を解決するため、これらの関数では引数に&t
のように明示的にアドレスを渡すように修正されています。
また、Pow
やGcd
メソッドでは、レシーバがポインタ型(*Natural
)であるにもかかわらず、メソッド内でx := *xp
のようにデリファレンスして値型としてx
を宣言しています。これは、メソッド内でx
を操作する際に、x
が値型であることによってコンパイラのバグを回避しつつ、必要に応じてx
のコピーを作成して操作することで、元のレシーバの状態を保護する意図があると考えられます。
//BUG
コメントは、これらの変更が一時的なワークアラウンドであり、コンパイラのバグが修正されれば、よりシンプルで効率的な元のポインタベースのコードに戻すことができるという開発者の意図を示しています。これは、Go言語の初期開発における試行錯誤と、コンパイラの成熟を待つ期間のコード管理戦略を反映しています。
総じて、このコミットは、Go言語の初期段階におけるコンパイラの制約と、それに対応するためのコードベースの適応、そして言語設計の進化の過程を垣間見ることができる貴重な例と言えます。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/doc/
- Go言語の初期の歴史に関する情報: https://go.dev/blog/history
- Go言語の
math/big
パッケージ(現在の多倍長整数ライブラリ): https://pkg.go.dev/math/big
参考にした情報源リンク
- コミット情報:
/home/orange/Project/comemo/commit_data/1393.txt
- GitHub上のコミットページ: https://github.com/golang/go/commit/9ccf39bd684d5d6384259cedd9ee0f567796225b
- Go言語の歴史に関する公式ブログ記事: https://go.dev/blog/history
- Go言語のコンパイラに関する一般的な情報(
6g
などの初期コンパイラについても言及がある場合がある): https://go.dev/doc/install/source (これは現在のビルド方法だが、歴史的背景を辿る手がかりになる) - Go言語における値渡しとポインタ渡しに関する一般的な解説記事(Goのセマンティクス理解のため)
- 例: https://go.dev/blog/effective-go (Effective GoはGoのイディオムを理解する上で非常に重要)
- 例: https://go.dev/tour/moretypes/8 (Go Tourのポインタに関するセクション)
- 例: https://go.dev/blog/go-slices-usage-and-internals (スライスの内部構造に関する公式ブログ記事)
- Go言語のIssueトラッカーやメーリングリスト(当時の
6g
バグに関する議論が見つかる可能性)
これらの情報源を総合的に参照し、コミットの背景、技術的詳細、およびGo言語の進化におけるその位置づけを分析しました。