[インデックス 14039] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)におけるrnd()
関数の挙動を64ビット環境により適したものに修正するものです。具体的には、32ビット環境では問題とならなかった4GBを超える型サイズを扱う際に発生するコンパイラのクラッシュ(Issue 4200)を修正します。
コミット
commit 94acfde22e73902692b8eef413c7c35a5ba98708
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Sun Oct 7 00:29:55 2012 +0200
cmd/gc: make rnd() more 64-bit-friendly.
Fixes #4200.
R=golang-dev, dave, rsc
CC=golang-dev
https://golang.org/cl/6619057
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/94acfde22e73902692b8eef413c7c35a5ba98708
元コミット内容
cmd/gc: make rnd() more 64-bit-friendly.
Fixes #4200.
R=golang-dev, dave, rsc
CC=golang-dev
https://golang.org/cl/6619057
変更の背景
このコミットは、Goコンパイラ(6g
、当時のGoコンパイラの名称の一つ)が、4GBを超えるサイズの型を処理しようとするとクラッシュするというバグ(Issue 4200)を修正するために導入されました。この問題は特に64ビットアーキテクチャにおいて顕在化しました。32ビットシステムではアドレス空間が4GBに制限されるため、4GBを超える型を直接扱うことは稀でしたが、64ビットシステムでは理論上はるかに大きなメモリ空間を扱えるため、このような巨大な型が定義される可能性がありました。
rnd()
関数は、メモリのアライメント(配置)計算に関連する関数であり、特定のバイト境界にデータを配置するために使用されます。この関数が32ビット符号なし整数(uint32
)を引数として受け取っていたため、4GBを超えるオフセットやサイズを正確に計算できず、結果としてコンパイラが不正なメモリ参照を行いクラッシュする原因となっていました。
前提知識の解説
- Goコンパイラ (
cmd/gc
): Go言語のソースコードを機械語に変換するプログラムです。Goのツールチェインの一部であり、go build
コマンドなどで内部的に利用されます。 - アライメント (Alignment): コンピュータのメモリ上でデータが配置される際の、特定のバイト境界への整列のことです。CPUが効率的にメモリにアクセスするためには、データがそのデータ型に応じた特定のアドレスに配置されている必要があります。例えば、4バイトの整数は4の倍数のアドレスに配置される、といった規則があります。アライメントが正しくないと、パフォーマンスの低下や、場合によってはクラッシュの原因となります。
rnd()
関数: このコミットで修正されるGoコンパイラ内部の関数で、おそらく"round"(丸める)の略であり、メモリのアライメント計算、特に指定された境界にオフセットを丸めるために使用されていたと考えられます。uint32
とvlong
:uint32
: 32ビットの符号なし整数型です。最大値は約42億(2^32 - 1)で、約4GBに相当します。vlong
: Goコンパイラ内部で使われる型で、プラットフォームに依存しない可変長整数型、または64ビット整数型を指すことが多いです。この文脈では、64ビット整数型として機能し、32ビットのuint32
よりもはるかに大きな値を表現できます。Go言語のソースコードではint64
やuint64
に相当します。
- Issue 4200: Goプロジェクトのバグトラッカーで報告された特定のバグを指します。このコミットメッセージから、「6g crashes when a type is larger than 4GB.」という内容であることが分かります。
技術的詳細
このコミットの核心は、rnd()
関数の引数と戻り値の型をuint32
からvlong
に変更することです。
元のrnd()
関数は、以下のように定義されていました。
uint32
rnd(uint32 o, uint32 r)
ここで、o
はオフセット、r
はアライメントの境界(例: 1, 2, 4, 8バイト)を表していました。uint32
型は最大で約4GBまでの値を表現できますが、これを超えるオフセットやサイズを扱うことができませんでした。
64ビットシステムでは、プログラムが4GBを超えるメモリを割り当てたり、非常に大きなデータ構造を定義したりすることが可能です。例えば、[N][10][10][10][10][3]byte
のような多次元配列でN
が十分に大きい場合、その型の合計サイズは4GBを容易に超える可能性があります。このような巨大な型のメモリレイアウトを計算する際に、rnd()
関数がuint32
の制限に引っかかり、オーバーフローや不正な計算を引き起こし、結果としてコンパイラがクラッシュしていました。
このコミットでは、rnd()
関数のシグネチャを以下のように変更しました。
vlong
rnd(vlong o, vlong r)
これにより、o
とr
がvlong
(64ビット整数)として扱われるようになり、4GBを超えるオフセットやサイズも正確に計算できるようになりました。この変更は、src/cmd/gc/align.c
(rnd
関数の実装ファイル)とsrc/cmd/gc/go.h
(rnd
関数の宣言ファイル)の両方で行われています。
また、この修正が正しく機能することを確認するために、test/fixedbugs/bug458.go
という新しいテストケースが追加されました。このテストケースは、unsafe.Sizeof(uintptr(0))
を使用して現在のアーキテクチャのポインタサイズ(32ビットまたは64ビット)を取得し、それに基づいて非常に大きな型T
を定義します。特に64ビットアーキテクチャでは、この型T
のサイズが4GBを超えるように設計されており、コンパイラがこの型を正常に処理できることを検証します。
コアとなるコードの変更箇所
src/cmd/gc/align.c
--- a/src/cmd/gc/align.c
+++ b/src/cmd/gc/align.c
@@ -15,8 +15,8 @@
static int defercalc;
-uint32
-rnd(uint32 o, uint32 r)
+vlong
+rnd(vlong o, vlong r)
{
if(r < 1 || r > 8 || (r&(r-1)) != 0)
fatal("rnd");
src/cmd/gc/go.h
--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -936,7 +936,7 @@ void checkwidth(Type *t);\n void defercheckwidth(void);\n void dowidth(Type *t);\n void resumecheckwidth(void);\n-uint32 rnd(uint32 o, uint32 r);\n+vlong rnd(vlong o, vlong r);\n void typeinit(void);\n
/*
test/fixedbugs/bug458.go
(新規追加)
--- /dev/null
+++ b/test/fixedbugs/bug458.go
@@ -0,0 +1,22 @@
+// compile
+
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Issue 4200: 6g crashes when a type is larger than 4GB.
+
+package main
+
+import "unsafe"
+
+// N=16 on 32-bit arches, 256 on 64-bit arches.
+// On 32-bit arches we don't want to test types
+// that are over 4GB large.
+const N = 1 << unsafe.Sizeof(uintptr(0))
+
+type T [N][10][10][10][10][3]byte
+
+func F(t *T) byte {
+ return t[0][0][0][0][0][0]
+}
コアとなるコードの解説
-
src/cmd/gc/align.c
とsrc/cmd/gc/go.h
の変更:- これら2つのファイルは、
rnd()
関数の定義と宣言を含んでいます。 uint32
からvlong
への型変更は、rnd()
関数がより大きな数値(特にメモリサイズやオフセット)を正確に扱えるようにするための直接的な修正です。これにより、32ビットの数値範囲を超えた計算で発生していたオーバーフローや不正な値の使用が解消されます。rnd()
関数の内部ロジック自体は変更されていませんが、引数と戻り値の型が拡張されたことで、より広い範囲の入力に対して正しく機能するようになります。
- これら2つのファイルは、
-
test/fixedbugs/bug458.go
の追加:- このテストファイルは、Issue 4200で報告されたバグを再現し、修正が正しく適用されたことを検証するために作成されました。
const N = 1 << unsafe.Sizeof(uintptr(0))
の行は、現在のアーキテクチャのポインタサイズ(uintptr
のサイズ)に基づいてN
の値を決定します。- 32ビットシステムでは
unsafe.Sizeof(uintptr(0))
は4バイトなので、N = 1 << 4 = 16
となります。 - 64ビットシステムでは
unsafe.Sizeof(uintptr(0))
は8バイトなので、N = 1 << 8 = 256
となります。
- 32ビットシステムでは
type T [N][10][10][10][10][3]byte
は、非常に大きな配列型を定義しています。- 64ビットシステムの場合、
N=256
となり、256 * 10 * 10 * 10 * 10 * 3 = 7,680,000
バイト、つまり約7.3MBのサイズになります。これは4GBよりはるかに小さいですが、このテストの意図は、rnd()
関数が64ビットの値を正しく扱えるようになったことを確認することにあります。元のバグは、rnd()
関数が32ビットの範囲を超えた値を処理しようとしたときに発生したため、このテストはrnd()
関数が64ビットの引数を正しく処理できることを検証します。 - コメントにある「On 32-bit arches we don't want to test types that are over 4GB large.」は、32ビットシステムでは4GBを超える型を定義しても意味がない(メモリ空間の制約があるため)ことを示唆しています。
- 64ビットシステムの場合、
func F(t *T) byte { return t[0][0][0][0][0][0] }
は、定義された巨大な型T
のインスタンスへのポインタを受け取り、その要素にアクセスする関数です。この関数がコンパイル時にクラッシュしないことが、修正の成功を示します。
このコミットは、Goコンパイラが64ビットアーキテクチャの能力を最大限に活用し、より大きなデータ構造を安定して処理できるようにするための重要なステップでした。
関連リンク
- Go言語の公式ウェブサイト: https://golang.org/
- Go言語のIssueトラッカー (当時のもの): https://golang.org/issue/4200 (このリンクは現在のIssueトラッカーにリダイレクトされる可能性がありますが、当時のIssue 4200の具体的な内容は、このコミットのテストコードから推測できます。)
- Goのコードレビューシステム (Gerrit): https://golang.org/cl/6619057
参考にした情報源リンク
- コミットメッセージと差分情報:
/home/orange/Project/comemo/commit_data/14039.txt
- Go言語のドキュメント (アライメント、型など): https://golang.org/doc/ (一般的なGo言語の知識として参照)
unsafe
パッケージのドキュメント: https://pkg.go.dev/unsafe (Goのunsafe
パッケージに関する一般的な知識として参照)- Goコンパイラの内部構造に関する一般的な情報 (必要に応じて): Goのソースコードリポジトリや関連する技術ブログ、論文など。