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

[インデックス 11802] ファイルの概要

このコミットは、Go言語のランタイムにおけるSelect文のメモリ割り当てに関するバグ修正です。具体的には、Select構造体内でlockorderpollorderという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構造体内でlockorderpollorderという2つのフィールドがメモリ上で連続して割り当てられる際に、その順序が原因でlockorderが正しくアラインメントされない可能性がありました。

lockorderはポインタのリストであり、pollorderuint16のリストです。一般的に、ポインタはシステムのアドレス幅(32ビットシステムでは4バイト、64ビットシステムでは8バイト)にアラインメントされる必要があります。一方、uint16は2バイトのデータ型です。もしpollorderが先に割り当てられ、その直後にlockorderが続く場合、pollorderのサイズ(uint16の数 × 2バイト)によっては、lockorderの開始アドレスがポインタのアラインメント要件を満たさない奇数アドレスや、ポインタサイズに満たないアドレスになる可能性がありました。

この問題は、gccgoコンパイラ(GoのコードをGCCのフロントエンドとしてコンパイルするプロジェクト)を使用してSPARCアーキテクチャ上でGoのコードを実行した際に発見されました。SPARCのような一部のアーキテクチャでは、メモリのアラインメント要件が厳格であり、アラインメントされていないメモリアクセスはパフォーマンスの低下だけでなく、プログラムのクラッシュを引き起こす可能性があります。

このコミットは、lockorderpollorderの前に配置することで、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);

ここで問題となるのは、pollorderuint16の配列であることです。uint16は2バイトのデータ型であり、pollorderのサイズはsize * sizeof(uint16)(つまりsize * 2バイト)となります。もしsizeが奇数であったり、pollorderの合計サイズがポインタのアラインメント要件(例えば8バイト境界)を満たさない場合、その直後に続くlockorderの開始アドレスが適切にアラインメントされない可能性がありました。

lockorderはポインタの配列であり、ポインタは通常、そのシステムのアドレス幅(例えば64ビットシステムでは8バイト)にアラインメントされる必要があります。SPARCのような厳格なアラインメント要件を持つアーキテクチャでは、アラインメントされていないポインタへのアクセスは、ハードウェアトラップを引き起こし、プログラムのクラッシュにつながります。

このコミットでは、lockorderpollorderの割り当て順序を入れ替えることでこの問題を解決しています。

// 変更後
sel->lockorder = (void*)(sel->scase + size);
sel->pollorder = (void*)(sel->lockorder + size);

この変更により、scaseの直後にlockorder(ポインタの配列)が配置されます。scaseの要素は通常、ポインタサイズにアラインメントされるため、その直後に続くlockorderも適切にアラインメントされる可能性が高まります。lockorderが適切にアラインメントされた後、その直後にpollorderuint16の配列)が続きます。pollorderuint16であるため、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関数内で行われています。

  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
    

    この新しいコメントは、メモリ割り当ての意図された順序を明確にしています。Select構造体、scaselockorderpollorderが単一の連続したメモリブロック内でどのように配置されるべきかを示しています。これは、以前のコードがこの意図に反していたことを示唆しています。

  2. ポインタの割り当て順序の変更:

    -	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が適切にアラインメントされれば、その後に続くpollorderuint16の配列)は、uint16のアラインメント要件(2バイト境界)を満たすことが容易になります。これにより、SPARCのような厳格なアラインメント要件を持つアーキテクチャ上でのアラインメントエラーが回避されます。

関連リンク

参考にした情報源リンク

  • Go言語のselect文に関する公式ドキュメントやチュートリアル
  • メモリのアラインメントに関する一般的なコンピュータアーキテクチャの資料
  • SPARCアーキテクチャのメモリモデルとアラインメント要件に関する資料
  • gccgoに関する情報(GCCのドキュメントなど)
  • Goランタイムのソースコード(特にsrc/pkg/runtime/chan.c
  • Goのチャネルとselectの内部実装に関する技術記事やブログポスト
  • Goのメモリ割り当てに関する技術記事# [インデックス 11802] ファイルの概要

このコミットは、Go言語のランタイムにおけるSelect文のメモリ割り当てに関するバグ修正です。具体的には、Select構造体内でlockorderpollorderという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構造体内でlockorderpollorderという2つのフィールドがメモリ上で連続して割り当てられる際に、その順序が原因でlockorderが正しくアラインメントされない可能性がありました。

lockorderはポインタのリストであり、pollorderuint16のリストです。一般的に、ポインタはシステムのアドレス幅(32ビットシステムでは4バイト、64ビットシステムでは8バイト)にアラインメントされる必要があります。一方、uint16は2バイトのデータ型です。もしpollorderが先に割り当てられ、その直後にlockorderが続く場合、pollorderのサイズ(uint16の数 × 2バイト)によっては、lockorderの開始アドレスがポインタのアラインメント要件を満たさない奇数アドレスや、ポインタサイズに満たないアドレスになる可能性がありました。

この問題は、gccgoコンパイラ(GoのコードをGCCのフロントエンドとしてコンパイルするプロジェクト)を使用してSPARCアーキテクチャ上でGoのコードを実行した際に発見されました。SPARCのような一部のアーキテクチャでは、メモリのアラインメント要件が厳格であり、アラインメントされていないメモリアクセスはパフォーマンスの低下だけでなく、プログラムのクラッシュを引き起こす可能性があります。

このコミットは、lockorderpollorderの前に配置することで、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);

ここで問題となるのは、pollorderuint16の配列であることです。uint16は2バイトのデータ型であり、pollorderのサイズはsize * sizeof(uint16)(つまりsize * 2バイト)となります。もしsizeが奇数であったり、pollorderの合計サイズがポインタのアラインメント要件(例えば8バイト境界)を満たさない場合、その直後に続くlockorderの開始アドレスが適切にアラインメントされない可能性がありました。

lockorderはポインタの配列であり、ポインタは通常、そのシステムのアドレス幅(例えば64ビットシステムでは8バイト)にアラインメントされる必要があります。SPARCのような厳格なアラインメント要件を持つアーキテクチャでは、アラインメントされていないポインタへのアクセスは、ハードウェアトラップを引き起こし、プログラムのクラッシュにつながります。

このコミットでは、lockorderpollorderの割り当て順序を入れ替えることでこの問題を解決しています。

// 変更後
sel->lockorder = (void*)(sel->scase + size);
sel->pollorder = (void*)(sel->lockorder + size);

この変更により、scaseの直後にlockorder(ポインタの配列)が配置されます。scaseの要素は通常、ポインタサイズにアラインメントされるため、その直後に続くlockorderも適切にアラインメントされる可能性が高まります。lockorderが適切にアラインメントされた後、その直後にpollorderuint16の配列)が続きます。pollorderuint16であるため、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関数内で行われています。

  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
    

    この新しいコメントは、メモリ割り当ての意図された順序を明確にしています。Select構造体、scaselockorderpollorderが単一の連続したメモリブロック内でどのように配置されるべきかを示しています。これは、以前のコードがこの意図に反していたことを示唆しています。

  2. ポインタの割り当て順序の変更:

    -	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が適切にアラインメントされれば、その後に続くpollorderuint16の配列)は、uint16のアラインメント要件(2バイト境界)を満たすことが容易になります。これにより、SPARCのような厳格なアラインメント要件を持つアーキテクチャ上でのアラインメントエラーが回避されます。

関連リンク

参考にした情報源リンク

  • Go言語のselect文に関する公式ドキュメントやチュートリアル
  • メモリのアラインメントに関する一般的なコンピュータアーキテクチャの資料
  • SPARCアーキテクチャのメモリモデルとアラインメント要件に関する資料
  • gccgoに関する情報(GCCのドキュメントなど)
  • Goランタイムのソースコード(特にsrc/pkg/runtime/chan.c
  • Goのチャネルとselectの内部実装に関する技術記事やブログポスト
  • Goのメモリ割り当てに関する技術記事