[インデックス 11802] ファイルの概要
このコミットは、Go言語のランタイムにおけるSelect
文のメモリ割り当てに関するバグ修正です。具体的には、Select
構造体内でlockorder
とpollorder
という2つのフィールドのメモリ配置順序を変更することで、lockorder
が誤ってアラインメントされる問題を解決しています。この問題は、gccgo
コンパイラを使用してSPARCアーキテクチャ上でコードを実行した際に発見されました。
コミット
commit 53e139c7a058c0491716e1fca96e33ee850ac15c
Author: Ian Lance Taylor <iant@golang.org>
Date: Fri Feb 10 21:24:14 2012 -0800
runtime: put lockorder before pollorder in Select memory block.
Otherwise lockorder may be misaligned, since lockorder is a
list of pointers and pollorder is a list of uint16.
Discovered running gccgo (which uses a modified copy of this
code) on SPARC.
R=golang-dev, gri
CC=golang-dev
https://golang.org/cl/5655054
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/53e139c7a058c0491716e1fca96e33ee850ac15c
元コミット内容
runtime: put lockorder before pollorder in Select memory block.
Otherwise lockorder may be misaligned, since lockorder is a
list of pointers and pollorder is a list of uint16.
Discovered running gccgo (which uses a modified copy of this
code) on SPARC.
R=golang-dev, gri
CC=golang-dev
https://golang.org/cl/5655054
変更の背景
この変更は、GoランタイムのSelect
文に関連するメモリ割り当てのバグを修正するために行われました。具体的には、Select
構造体内でlockorder
とpollorder
という2つのフィールドがメモリ上で連続して割り当てられる際に、その順序が原因でlockorder
が正しくアラインメントされない可能性がありました。
lockorder
はポインタのリストであり、pollorder
はuint16
のリストです。一般的に、ポインタはシステムのアドレス幅(32ビットシステムでは4バイト、64ビットシステムでは8バイト)にアラインメントされる必要があります。一方、uint16
は2バイトのデータ型です。もしpollorder
が先に割り当てられ、その直後にlockorder
が続く場合、pollorder
のサイズ(uint16
の数 × 2バイト)によっては、lockorder
の開始アドレスがポインタのアラインメント要件を満たさない奇数アドレスや、ポインタサイズに満たないアドレスになる可能性がありました。
この問題は、gccgo
コンパイラ(GoのコードをGCCのフロントエンドとしてコンパイルするプロジェクト)を使用してSPARCアーキテクチャ上でGoのコードを実行した際に発見されました。SPARCのような一部のアーキテクチャでは、メモリのアラインメント要件が厳格であり、アラインメントされていないメモリアクセスはパフォーマンスの低下だけでなく、プログラムのクラッシュを引き起こす可能性があります。
このコミットは、lockorder
をpollorder
の前に配置することで、lockorder
が常に適切なアラインメントで開始されるようにし、この問題を解決することを目的としています。
前提知識の解説
GoのSelect
文
Go言語のselect
文は、複数のチャネル操作を待機し、準備ができた最初のチャネル操作を実行するための強力な制御構造です。select
文は、Goの並行処理モデルにおいて、デッドロックを回避し、複数のゴルーチン間の協調を可能にするために不可欠です。
内部的には、select
文はGoランタイムによって処理されます。select
が実行されると、ランタイムは関連するチャネルの準備状況を監視し、いずれかのチャネルが送受信可能になったときに、対応するケースを実行します。この監視と実行のメカニズムは、runtime
パッケージ内のSelect
構造体と関連する関数によって管理されます。
メモリのアラインメント
メモリのアラインメントとは、コンピュータのメモリ上でデータが特定の境界に配置されることを指します。ほとんどのコンピュータアーキテクチャでは、CPUがメモリからデータを効率的に読み書きするために、データ型がそのサイズまたはワードサイズ(CPUが一度に処理できるデータの単位)の倍数のアドレスに配置されている必要があります。
例えば、4バイトの整数は4の倍数のアドレス(0x00, 0x04, 0x08など)に配置されるのが理想的です。もしデータがアラインメントされていないアドレス(例えば0x01)に配置されている場合、CPUはデータを読み取るために複数のメモリアクセスを行う必要があったり、アラインメントエラーを発生させたりする可能性があります。
- ポインタのアラインメント: ポインタはメモリアドレスを格納するため、通常はシステムのアドレス幅(32ビットシステムでは4バイト、64ビットシステムでは8バイト)にアラインメントされる必要があります。
uint16
のアラインメント:uint16
は2バイトのデータ型であり、通常は2バイト境界にアラインメントされます。
SPARCアーキテクチャ
SPARC (Scalable Processor Architecture) は、Sun Microsystemsによって開発されたRISC (Reduced Instruction Set Computer) 命令セットアーキテクチャです。SPARCプロセッサは、厳格なメモリのアラインメント要件を持つことで知られています。これは、アラインメントされていないメモリアクセスがハードウェア例外(トラップ)を引き起こし、プログラムのクラッシュや予期せぬ動作につながる可能性があることを意味します。
gccgo
gccgo
は、Go言語のフロントエンドとしてGCC (GNU Compiler Collection) を使用するGoコンパイラの実装です。Goの公式コンパイラ(gc
)とは異なり、gccgo
はGCCの最適化バックエンドを利用できるため、異なるアーキテクチャやシステムでのGoプログラムのコンパイルと実行を可能にします。このコミットで問題が発見されたのは、gccgo
がGoランタイムのコードの修正されたコピーを使用しており、SPARCアーキテクチャの厳格なアラインメント要件が露呈したためです。
技術的詳細
このコミットの技術的詳細は、Goランタイムのsrc/pkg/runtime/chan.c
ファイル内のnewselect
関数におけるメモリ割り当ての変更に集約されます。
newselect
関数は、select
文の実行に必要なSelect
構造体とその関連データを動的に割り当てる役割を担っています。この関数は、runtime·mal
(Goランタイムのメモリ割り当て関数)を使用して、Select
構造体自体、scase
(チャネルケースの配列)、lockorder
(ロック順序を管理するポインタの配列)、そしてpollorder
(ポーリング順序を管理するuint16
の配列)を単一の連続したメモリブロックとして割り当てようとします。
変更前のコードでは、メモリブロック内でscase
の直後にpollorder
が配置され、その後にlockorder
が配置されていました。
// 変更前
sel->pollorder = (void*)(sel->scase + size);
sel->lockorder = (void*)(sel->pollorder + size);
ここで問題となるのは、pollorder
がuint16
の配列であることです。uint16
は2バイトのデータ型であり、pollorder
のサイズはsize * sizeof(uint16)
(つまりsize * 2
バイト)となります。もしsize
が奇数であったり、pollorder
の合計サイズがポインタのアラインメント要件(例えば8バイト境界)を満たさない場合、その直後に続くlockorder
の開始アドレスが適切にアラインメントされない可能性がありました。
lockorder
はポインタの配列であり、ポインタは通常、そのシステムのアドレス幅(例えば64ビットシステムでは8バイト)にアラインメントされる必要があります。SPARCのような厳格なアラインメント要件を持つアーキテクチャでは、アラインメントされていないポインタへのアクセスは、ハードウェアトラップを引き起こし、プログラムのクラッシュにつながります。
このコミットでは、lockorder
とpollorder
の割り当て順序を入れ替えることでこの問題を解決しています。
// 変更後
sel->lockorder = (void*)(sel->scase + size);
sel->pollorder = (void*)(sel->lockorder + size);
この変更により、scase
の直後にlockorder
(ポインタの配列)が配置されます。scase
の要素は通常、ポインタサイズにアラインメントされるため、その直後に続くlockorder
も適切にアラインメントされる可能性が高まります。lockorder
が適切にアラインメントされた後、その直後にpollorder
(uint16
の配列)が続きます。pollorder
はuint16
であるため、2バイト境界にアラインメントされていれば問題ありません。ポインタのアラインメント要件は通常2バイト境界よりも厳しいため、lockorder
が適切にアラインメントされていれば、その直後のpollorder
も自然と2バイト境界にアラインメントされることになります。
この修正は、メモリ割り当ての順序を調整することで、異なるデータ型のアラインメント要件の衝突を回避し、特に厳格なアラインメント要件を持つアーキテクチャ上でのGoランタイムの安定性を向上させるものです。
コアとなるコードの変更箇所
--- a/src/pkg/runtime/chan.c
+++ b/src/pkg/runtime/chan.c
@@ -586,6 +586,10 @@ newselect(int32 size, Select **selp)
if(size > 1)
n = size-1;
+ // allocate all the memory we need in a single allocation
+ // start with Select with size cases
+ // then lockorder with size entries
+ // then pollorder with size entries
sel = runtime·mal(sizeof(*sel) +
n*sizeof(sel->scase[0]) +
size*sizeof(sel->lockorder[0]) +
@@ -593,8 +597,8 @@ newselect(int32 size, Select **selp)
sel->tcase = size;
sel->ncase = 0;
- sel->pollorder = (void*)(sel->scase + size);
- sel->lockorder = (void*)(sel->pollorder + size);
+ sel->lockorder = (void*)(sel->scase + size);
+ sel->pollorder = (void*)(sel->lockorder + size);
*selp = sel;
if(debug)
コアとなるコードの解説
変更はsrc/pkg/runtime/chan.c
ファイルのnewselect
関数内で行われています。
-
コメントの追加:
+ // allocate all the memory we need in a single allocation + // start with Select with size cases + // then lockorder with size entries + // then pollorder with size entries
この新しいコメントは、メモリ割り当ての意図された順序を明確にしています。
Select
構造体、scase
、lockorder
、pollorder
が単一の連続したメモリブロック内でどのように配置されるべきかを示しています。これは、以前のコードがこの意図に反していたことを示唆しています。 -
ポインタの割り当て順序の変更:
- sel->pollorder = (void*)(sel->scase + size); - sel->lockorder = (void*)(sel->pollorder + size); + sel->lockorder = (void*)(sel->scase + size); + sel->pollorder = (void*)(sel->lockorder + size);
これがこのコミットの核心的な変更です。
- 変更前は、
sel->scase
の直後にsel->pollorder
が配置され、その後にsel->lockorder
が配置されていました。 - 変更後は、
sel->scase
の直後にsel->lockorder
が配置され、その後にsel->pollorder
が配置されるようになりました。
- 変更前は、
この順序の変更により、ポインタの配列であるlockorder
が、より厳格なアラインメント要件を持つscase
の直後に配置されることで、適切なメモリ境界にアラインメントされる可能性が高まります。lockorder
が適切にアラインメントされれば、その後に続くpollorder
(uint16
の配列)は、uint16
のアラインメント要件(2バイト境界)を満たすことが容易になります。これにより、SPARCのような厳格なアラインメント要件を持つアーキテクチャ上でのアラインメントエラーが回避されます。
関連リンク
- Go CL 5655054: https://golang.org/cl/5655054
参考にした情報源リンク
- Go言語の
select
文に関する公式ドキュメントやチュートリアル - メモリのアラインメントに関する一般的なコンピュータアーキテクチャの資料
- SPARCアーキテクチャのメモリモデルとアラインメント要件に関する資料
gccgo
に関する情報(GCCのドキュメントなど)- Goランタイムのソースコード(特に
src/pkg/runtime/chan.c
) - Goのチャネルと
select
の内部実装に関する技術記事やブログポスト - Goのメモリ割り当てに関する技術記事# [インデックス 11802] ファイルの概要
このコミットは、Go言語のランタイムにおけるSelect
文のメモリ割り当てに関するバグ修正です。具体的には、Select
構造体内でlockorder
とpollorder
という2つのフィールドのメモリ配置順序を変更することで、lockorder
が誤ってアラインメントされる問題を解決しています。この問題は、gccgo
コンパイラを使用してSPARCアーキテクチャ上でコードを実行した際に発見されました。
コミット
commit 53e139c7a058c0491716e1fca96e33ee850ac15c
Author: Ian Lance Taylor <iant@golang.org>
Date: Fri Feb 10 21:24:14 2012 -0800
runtime: put lockorder before pollorder in Select memory block.
Otherwise lockorder may be misaligned, since lockorder is a
list of pointers and pollorder is a list of uint16.
Discovered running gccgo (which uses a modified copy of this
code) on SPARC.
R=golang-dev, gri
CC=golang-dev
https://golang.org/cl/5655054
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/53e139c7a058c0491716e1fca96e33ee850ac15c
元コミット内容
runtime: put lockorder before pollorder in Select memory block.
Otherwise lockorder may be misaligned, since lockorder is a
list of pointers and pollorder is a list of uint16.
Discovered running gccgo (which uses a modified copy of this
code) on SPARC.
R=golang-dev, gri
CC=golang-dev
https://golang.org/cl/5655054
変更の背景
この変更は、GoランタイムのSelect
文に関連するメモリ割り当てのバグを修正するために行われました。具体的には、Select
構造体内でlockorder
とpollorder
という2つのフィールドがメモリ上で連続して割り当てられる際に、その順序が原因でlockorder
が正しくアラインメントされない可能性がありました。
lockorder
はポインタのリストであり、pollorder
はuint16
のリストです。一般的に、ポインタはシステムのアドレス幅(32ビットシステムでは4バイト、64ビットシステムでは8バイト)にアラインメントされる必要があります。一方、uint16
は2バイトのデータ型です。もしpollorder
が先に割り当てられ、その直後にlockorder
が続く場合、pollorder
のサイズ(uint16
の数 × 2バイト)によっては、lockorder
の開始アドレスがポインタのアラインメント要件を満たさない奇数アドレスや、ポインタサイズに満たないアドレスになる可能性がありました。
この問題は、gccgo
コンパイラ(GoのコードをGCCのフロントエンドとしてコンパイルするプロジェクト)を使用してSPARCアーキテクチャ上でGoのコードを実行した際に発見されました。SPARCのような一部のアーキテクチャでは、メモリのアラインメント要件が厳格であり、アラインメントされていないメモリアクセスはパフォーマンスの低下だけでなく、プログラムのクラッシュを引き起こす可能性があります。
このコミットは、lockorder
をpollorder
の前に配置することで、lockorder
が常に適切なアラインメントで開始されるようにし、この問題を解決することを目的としています。
前提知識の解説
GoのSelect
文
Go言語のselect
文は、複数のチャネル操作を待機し、準備ができた最初のチャネル操作を実行するための強力な制御構造です。select
文は、Goの並行処理モデルにおいて、デッドロックを回避し、複数のゴルーチン間の協調を可能にするために不可欠です。
内部的には、select
文はGoランタイムによって処理されます。select
が実行されると、ランタイムは関連するチャネルの準備状況を監視し、いずれかのチャネルが送受信可能になったときに、対応するケースを実行します。この監視と実行のメカニズムは、runtime
パッケージ内のSelect
構造体と関連する関数によって管理されます。
メモリのアラインメント
メモリのアラインメントとは、コンピュータのメモリ上でデータが特定の境界に配置されることを指します。ほとんどのコンピュータアーキテクチャでは、CPUがメモリからデータを効率的に読み書きするために、データ型がそのサイズまたはワードサイズ(CPUが一度に処理できるデータの単位)の倍数のアドレスに配置されている必要があります。
例えば、4バイトの整数は4の倍数のアドレス(0x00, 0x04, 0x08など)に配置されるのが理想的です。もしデータがアラインメントされていないアドレス(例えば0x01)に配置されている場合、CPUはデータを読み取るために複数のメモリアクセスを行う必要があったり、アラインメントエラーを発生させたりする可能性があります。
- ポインタのアラインメント: ポインタはメモリアドレスを格納するため、通常はシステムのアドレス幅(32ビットシステムでは4バイト、64ビットシステムでは8バイト)にアラインメントされる必要があります。
uint16
のアラインメント:uint16
は2バイトのデータ型であり、通常は2バイト境界にアラインメントされます。
SPARCアーキテクチャ
SPARC (Scalable Processor Architecture) は、Sun Microsystemsによって開発されたRISC (Reduced Instruction Set Computer) 命令セットアーキテクチャです。SPARCプロセッサは、厳格なメモリのアラインメント要件を持つことで知られています。これは、アラインメントされていないメモリアクセスがハードウェア例外(トラップ)を引き起こし、プログラムのクラッシュや予期せぬ動作につながる可能性があることを意味します。
gccgo
gccgo
は、Go言語のフロントエンドとしてGCC (GNU Compiler Collection) を使用するGoコンパイラの実装です。Goの公式コンパイラ(gc
)とは異なり、gccgo
はGCCの最適化バックエンドを利用できるため、異なるアーキテクチャやシステムでのGoプログラムのコンパイルと実行を可能にします。このコミットで問題が発見されたのは、gccgo
がGoランタイムのコードの修正されたコピーを使用しており、SPARCアーキテクチャの厳格なアラインメント要件が露呈したためです。
技術的詳細
このコミットの技術的詳細は、Goランタイムのsrc/pkg/runtime/chan.c
ファイル内のnewselect
関数におけるメモリ割り当ての変更に集約されます。
newselect
関数は、select
文の実行に必要なSelect
構造体とその関連データを動的に割り当てる役割を担っています。この関数は、runtime·mal
(Goランタイムのメモリ割り当て関数)を使用して、Select
構造体自体、scase
(チャネルケースの配列)、lockorder
(ロック順序を管理するポインタの配列)、そしてpollorder
(ポーリング順序を管理するuint16
の配列)を単一の連続したメモリブロックとして割り当てようとします。
変更前のコードでは、メモリブロック内でscase
の直後にpollorder
が配置され、その後にlockorder
が配置されていました。
// 変更前
sel->pollorder = (void*)(sel->scase + size);
sel->lockorder = (void*)(sel->pollorder + size);
ここで問題となるのは、pollorder
がuint16
の配列であることです。uint16
は2バイトのデータ型であり、pollorder
のサイズはsize * sizeof(uint16)
(つまりsize * 2
バイト)となります。もしsize
が奇数であったり、pollorder
の合計サイズがポインタのアラインメント要件(例えば8バイト境界)を満たさない場合、その直後に続くlockorder
の開始アドレスが適切にアラインメントされない可能性がありました。
lockorder
はポインタの配列であり、ポインタは通常、そのシステムのアドレス幅(例えば64ビットシステムでは8バイト)にアラインメントされる必要があります。SPARCのような厳格なアラインメント要件を持つアーキテクチャでは、アラインメントされていないポインタへのアクセスは、ハードウェアトラップを引き起こし、プログラムのクラッシュにつながります。
このコミットでは、lockorder
とpollorder
の割り当て順序を入れ替えることでこの問題を解決しています。
// 変更後
sel->lockorder = (void*)(sel->scase + size);
sel->pollorder = (void*)(sel->lockorder + size);
この変更により、scase
の直後にlockorder
(ポインタの配列)が配置されます。scase
の要素は通常、ポインタサイズにアラインメントされるため、その直後に続くlockorder
も適切にアラインメントされる可能性が高まります。lockorder
が適切にアラインメントされた後、その直後にpollorder
(uint16
の配列)が続きます。pollorder
はuint16
であるため、2バイト境界にアラインメントされていれば問題ありません。ポインタのアラインメント要件は通常2バイト境界よりも厳しいため、lockorder
が適切にアラインメントされていれば、その直後のpollorder
も自然と2バイト境界にアラインメントされることになります。
この修正は、メモリ割り当ての順序を調整することで、異なるデータ型のアラインメント要件の衝突を回避し、特に厳格なアラインメント要件を持つアーキテクチャ上でのGoランタイムの安定性を向上させるものです。
コアとなるコードの変更箇所
--- a/src/pkg/runtime/chan.c
+++ b/src/pkg/runtime/chan.c
@@ -586,6 +586,10 @@ newselect(int32 size, Select **selp)
if(size > 1)
n = size-1;
+ // allocate all the memory we need in a single allocation
+ // start with Select with size cases
+ // then lockorder with size entries
+ // then pollorder with size entries
sel = runtime·mal(sizeof(*sel) +
n*sizeof(sel->scase[0]) +
size*sizeof(sel->lockorder[0]) +
@@ -593,8 +597,8 @@ newselect(int32 size, Select **selp)
sel->tcase = size;
sel->ncase = 0;
- sel->pollorder = (void*)(sel->scase + size);
- sel->lockorder = (void*)(sel->pollorder + size);
+ sel->lockorder = (void*)(sel->scase + size);
+ sel->pollorder = (void*)(sel->lockorder + size);
*selp = sel;
if(debug)
コアとなるコードの解説
変更はsrc/pkg/runtime/chan.c
ファイルのnewselect
関数内で行われています。
-
コメントの追加:
+ // allocate all the memory we need in a single allocation + // start with Select with size cases + // then lockorder with size entries + // then pollorder with size entries
この新しいコメントは、メモリ割り当ての意図された順序を明確にしています。
Select
構造体、scase
、lockorder
、pollorder
が単一の連続したメモリブロック内でどのように配置されるべきかを示しています。これは、以前のコードがこの意図に反していたことを示唆しています。 -
ポインタの割り当て順序の変更:
- sel->pollorder = (void*)(sel->scase + size); - sel->lockorder = (void*)(sel->pollorder + size); + sel->lockorder = (void*)(sel->scase + size); + sel->pollorder = (void*)(sel->lockorder + size);
これがこのコミットの核心的な変更です。
- 変更前は、
sel->scase
の直後にsel->pollorder
が配置され、その後にsel->lockorder
が配置されていました。 - 変更後は、
sel->scase
の直後にsel->lockorder
が配置され、その後にsel->pollorder
が配置されるようになりました。
- 変更前は、
この順序の変更により、ポインタの配列であるlockorder
が、より厳格なアラインメント要件を持つscase
の直後に配置されることで、適切なメモリ境界にアラインメントされる可能性が高まります。lockorder
が適切にアラインメントされれば、その後に続くpollorder
(uint16
の配列)は、uint16
のアラインメント要件(2バイト境界)を満たすことが容易になります。これにより、SPARCのような厳格なアラインメント要件を持つアーキテクチャ上でのアラインメントエラーが回避されます。
関連リンク
- Go CL 5655054: https://golang.org/cl/5655054
参考にした情報源リンク
- Go言語の
select
文に関する公式ドキュメントやチュートリアル - メモリのアラインメントに関する一般的なコンピュータアーキテクチャの資料
- SPARCアーキテクチャのメモリモデルとアラインメント要件に関する資料
gccgo
に関する情報(GCCのドキュメントなど)- Goランタイムのソースコード(特に
src/pkg/runtime/chan.c
) - Goのチャネルと
select
の内部実装に関する技術記事やブログポスト - Goのメモリ割り当てに関する技術記事