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

[インデックス 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"(丸める)の略であり、メモリのアライメント計算、特に指定された境界にオフセットを丸めるために使用されていたと考えられます。
  • uint32vlong:
    • uint32: 32ビットの符号なし整数型です。最大値は約42億(2^32 - 1)で、約4GBに相当します。
    • vlong: Goコンパイラ内部で使われる型で、プラットフォームに依存しない可変長整数型、または64ビット整数型を指すことが多いです。この文脈では、64ビット整数型として機能し、32ビットのuint32よりもはるかに大きな値を表現できます。Go言語のソースコードではint64uint64に相当します。
  • 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)

これにより、orvlong(64ビット整数)として扱われるようになり、4GBを超えるオフセットやサイズも正確に計算できるようになりました。この変更は、src/cmd/gc/align.crnd関数の実装ファイル)とsrc/cmd/gc/go.hrnd関数の宣言ファイル)の両方で行われています。

また、この修正が正しく機能することを確認するために、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.csrc/cmd/gc/go.h の変更:

    • これら2つのファイルは、rnd()関数の定義と宣言を含んでいます。
    • uint32からvlongへの型変更は、rnd()関数がより大きな数値(特にメモリサイズやオフセット)を正確に扱えるようにするための直接的な修正です。これにより、32ビットの数値範囲を超えた計算で発生していたオーバーフローや不正な値の使用が解消されます。
    • rnd()関数の内部ロジック自体は変更されていませんが、引数と戻り値の型が拡張されたことで、より広い範囲の入力に対して正しく機能するようになります。
  • 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となります。
    • 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を超える型を定義しても意味がない(メモリ空間の制約があるため)ことを示唆しています。
    • 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のソースコードリポジトリや関連する技術ブログ、論文など。