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

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

このコミットは、Goランタイムにおける386アーキテクチャ向けのビルド修正に関するものです。具体的には、cas64(Compare And Swap 64-bit)関数の変更後に発生したビルドエラーを修正しています。

コミット

commit f70a19f085bd4cd67f57e9d99af0a1959ffef4ab
Author: Russ Cox <rsc@golang.org>
Date:   Fri Jul 12 00:42:46 2013 -0400

    runtime: fix 386 build after cas64 change
    
    Missed this in CL 10909045.
    
    TBR=golang-dev
    CC=golang-dev
    https://golang.org/cl/10803045

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/f70a19f085bd4cd67f57e9d99af0a1959ffef4ab

元コミット内容

このコミットは、以前の変更(CL 10909045)によって導入されたcas64関数のシグネチャ変更に対応するための修正です。元のcas64関数のシグネチャは、old引数がポインタであったため、*oldとしてその値を参照していました。しかし、CL 10909045でold引数が値渡しに変更されたにもかかわらず、386アーキテクチャのアセンブリコードがその変更に追従していなかったため、ビルドエラーが発生していました。

変更の背景

Go言語のランタイムは、並行処理を効率的に行うためにアトミック操作を多用します。cas64(Compare And Swap 64-bit)はそのようなアトミック操作の一つで、マルチスレッド環境での共有データの整合性を保つために不可欠です。

このコミットの背景には、cas64関数の引数渡し方法の変更があります。以前のコミット(CL 10909045)で、runtime·cas64関数のold引数がuint64 *old(ポインタ)からuint64 old(値)に変更されました。この変更は、関数の呼び出し規約や内部実装に影響を与えます。

しかし、386アーキテクチャ向けのアセンブリコード(src/pkg/runtime/asm_386.s)がこの変更に追従していなかったため、cas64関数が期待する引数の位置と、実際にアセンブリコードが引数を読み込む位置との間に不整合が生じました。これにより、ビルド時にエラーが発生するか、あるいは実行時に不正な動作を引き起こす可能性がありました。

このコミットは、この不整合を解消し、386アーキテクチャ上でのGoランタイムのビルドが正しく行われるようにすることを目的としています。

前提知識の解説

  • アトミック操作 (Atomic Operations): マルチスレッド環境において、複数のスレッドから同時にアクセスされる共有データに対して、不可分(atomic)な操作を行うことです。これにより、データの破損や不整合を防ぎます。Compare And Swap (CAS) は、アトミック操作の代表的なもので、特定のメモリ位置の値が期待する値と一致する場合にのみ、新しい値に更新するという操作です。
  • Compare And Swap (CAS): CASは3つのオペランドを取ります。メモリ位置(V)、期待する古い値(A)、新しい値(B)です。操作は、Vの現在の値がAと等しい場合にのみ、VをBにアトミックに設定します。そうでない場合は、何も変更せず、Vの現在の値を返します。これにより、ロックを使用せずにスレッドセーフなデータ更新が可能になります。
  • 386アーキテクチャ (Intel 80386 Architecture): Intelが開発した32ビットのマイクロプロセッサアーキテクチャです。Go言語は様々なアーキテクチャをサポートしており、386はその一つです。各アーキテクチャにはそれぞれ異なるアセンブリ言語やレジスタの利用規約があります。
  • アセンブリ言語 (Assembly Language): コンピュータのプロセッサが直接実行できる機械語に非常に近い低レベルのプログラミング言語です。Goランタイムのようなシステムレベルのコードでは、パフォーマンスの最適化やハードウェアへの直接アクセスが必要な場合にアセンブリ言語が使用されます。
  • スタックポインタ (Stack Pointer - SP): プログラムのスタックの現在のトップを指すレジスタです。関数呼び出しの際に引数やローカル変数がスタックにプッシュされ、関数から戻る際にポップされます。引数は通常、スタックポインタからのオフセットでアクセスされます。
  • レジスタ (Registers): CPU内部にある高速な記憶領域で、演算やデータ転送の際に一時的にデータを保持します。386アーキテクチャでは、AX, DX, BX, CX, SI, BPなどの汎用レジスタがあります。
    • AX (Accumulator Register): 演算結果を格納するのに使われることが多い。
    • DX (Data Register): AXと組み合わせて64ビット値を扱う際などに使われる。
    • BX (Base Register): メモリのアドレス指定に使われることが多い。
    • CX (Count Register): ループカウンタなどに使われることが多い。
    • SI (Source Index Register): データ転送のソースアドレスに使われることが多い。
    • BP (Base Pointer Register): スタックフレームのベースアドレスを指す。
  • CMPXCHG8B 命令: Intel 386以降のプロセッサで利用可能な命令で、64ビットのCAS操作を実行します。EDX:EAXレジスタペアに期待する値、ECX:EBXレジスタペアに新しい値を設定し、指定されたメモリ位置の値をEDX:EAXと比較します。一致すればメモリ位置をECX:EBXで更新し、そうでなければメモリ位置の値をEDX:EAXにロードします。

技術的詳細

このコミットの核心は、runtime·cas64関数のアセンブリ実装における引数のオフセットの修正です。

元のruntime·cas64関数のC言語でのプロトタイプがbool runtime·cas64(uint64 *val, uint64 *old, uint64 new)であった場合、スタック上の引数の配置は以下のようになります(一般的な呼び出し規約を仮定):

  • val: 4(SP)
  • old: 8(SP)
  • new: 12(SP) (下位32ビット), 16(SP) (上位32ビット)

この場合、oldの値はポインタであるため、0(SI)4(SI)SIoldポインタのアドレス)を介してアクセスされます。

しかし、CL 10909045でプロトタイプがbool runtime·cas64(uint64 *val, uint64 old, uint64 new)に変更された場合、oldが値渡しになったため、スタック上の引数の配置が変わります。

  • val: 4(SP)
  • old: 8(SP) (下位32ビット), 12(SP) (上位32ビット)
  • new: 16(SP) (下位32ビット), 20(SP) (上位32ビット)

この変更により、アセンブリコードがoldnewの値をスタックから読み込む際のオフセットを調整する必要がありました。

具体的には、src/pkg/runtime/asm_386.s内のTEXT runtime·cas64(SB)セクションで、以下の変更が行われました。

  1. old引数の読み込み:

    • 変更前: MOVL 8(SP), SI (ポインタoldのアドレスをSIにロード)
    • 変更後: MOVL 8(SP), AX (値oldの下位32ビットをAXにロード)
    • 変更前: MOVL 0(SI), AX (ポインタoldが指す値の下位32ビットをAXにロード)
    • 変更後: MOVL 12(SP), DX (値oldの上位32ビットをDXにロード) これにより、AX:DXレジスタペアにoldの値が正しくロードされるようになります。
  2. new引数の読み込み:

    • 変更前: MOVL 12(SP), BX (値newの下位32ビットをBXにロード)
    • 変更後: MOVL 16(SP), BX (値newの下位32ビットをBXにロード)
    • 変更前: MOVL 16(SP), CX (値newの上位32ビットをCXにロード)
    • 変更後: MOVL 20(SP), CX (値newの上位32ビットをCXにロード) これにより、BX:CXレジスタペアにnewの値が正しくロードされるようになります。
  3. cas64_failラベル内の処理:

    • 変更前は、CAS操作が失敗した場合に*old = *valという処理を行うために、AXDX*valの現在の値)をSIが指すアドレス(oldポインタが指す場所)に書き戻していました。
    • 変更後は、oldが値渡しになったため、この書き戻し処理は不要となり、削除されました。oldは入力値であり、出力として変更されるものではないためです。

また、src/pkg/runtime/atomic_386.cでは、runtime·xchg64関数内でruntime·cas64を呼び出す際に、old引数に*addraddrが指す値)を渡すように修正されています。これは、runtime·cas64old引数が値渡しになったことに対応するものです。

コアとなるコードの変更箇所

src/pkg/runtime/asm_386.s

--- a/src/pkg/runtime/asm_386.s
+++ b/src/pkg/runtime/asm_386.s
@@ -319,30 +319,26 @@ TEXT runtime·cas(SB), 7, $0
 	MOVL	$1, AX
 	RET
 
-// bool runtime·cas64(uint64 *val, uint64 *old, uint64 new)
+// bool runtime·cas64(uint64 *val, uint64 old, uint64 new)
 // Atomically:
 //	if(*val == *old){
 //		*val = new;
 //		return 1;
 //	} else {
-//		*old = *val
 //		return 0;
 //	}
 TEXT runtime·cas64(SB), 7, $0
 	MOVL	4(SP), BP
-	MOVL	8(SP), SI
-	MOVL	0(SI), AX
-	MOVL	4(SI), DX
-	MOVL	12(SP), BX
-	MOVL	16(SP), CX
+	MOVL	8(SP), AX
+	MOVL	12(SP), DX
+	MOVL	16(SP), BX
+	MOVL	20(SP), CX
 	LOCK
 	CMPXCHG8B	0(BP)
 	JNZ	cas64_fail
 	MOVL	$1, AX
 	RET
 cas64_fail:
-	MOVL	AX, 0(SI)
-	MOVL	DX, 4(SI)
 	MOVL	$0, AX
 	RET

src/pkg/runtime/atomic_386.c

--- a/src/pkg/runtime/atomic_386.c
+++ b/src/pkg/runtime/atomic_386.c
@@ -38,7 +38,7 @@ runtime·xchg64(uint64 volatile* addr, uint64 v)
 	uint64 old;
 
 	do
-		old = addr;
+		old = *addr;
 	while(!runtime·cas64(addr, old, v));
 
 	return old;

コアとなるコードの解説

src/pkg/runtime/asm_386.s の変更点

  • コメントの更新: runtime·cas64関数のシグネチャを示すコメントが、uint64 *oldからuint64 oldへと変更されています。これは、old引数がポインタではなく値として渡されるようになったことを示しています。
  • 引数のオフセット調整:
    • MOVL 8(SP), SIMOVL 8(SP), AX に変更されました。これは、スタックオフセット8(SP)からold引数の下位32ビットをAXレジスタに直接ロードするように変更されたことを意味します。以前は、8(SP)にはoldポインタのアドレスが格納されており、そのアドレスをSIレジスタにロードしていました。
    • MOVL 0(SI), AXMOVL 4(SI), DX が削除されました。これらは、SIが指すアドレス(oldポインタが指す場所)からoldの値(64ビット)を読み込むための命令でした。oldが値渡しになったため、スタックから直接読み込むようになり、これらの間接参照は不要になりました。
    • MOVL 12(SP), BXMOVL 16(SP), BX に、MOVL 16(SP), CXMOVL 20(SP), CX に変更されました。これは、new引数の下位32ビットと上位32ビットをスタックから読み込む際のオフセットが、それぞれ4バイトずつ増加したことを意味します。これは、old引数がポインタから値に変わったことで、スタック上の引数の配置がずれたためです。
  • cas64_failラベル内の処理の削除:
    • MOVL AX, 0(SI)MOVL DX, 4(SI) が削除されました。これらの命令は、CMPXCHG8B命令が失敗した場合に、現在の*valの値を*oldに書き戻すためのものでした。しかし、oldが値渡しになったことで、oldは入力値であり、関数内で変更されるべきではないため、この処理は不要になりました。

src/pkg/runtime/atomic_386.c の変更点

  • runtime·xchg64関数内のdo-whileループ内で、old = addr;old = *addr; に変更されました。
    • 変更前は、addruint64 volatile*型)を直接olduint64型)に代入しようとしていました。これは型が一致しないため、コンパイルエラーになるか、あるいは不正な値が代入される可能性がありました。
    • 変更後は、*addrとすることで、addrが指すメモリ位置のuint64型の値をoldに代入しています。これにより、runtime·cas64関数に渡されるold引数が、期待される値(現在の*addrの値)となるように修正されました。これは、runtime·cas64old引数が値渡しになったことに対応するものです。

これらの変更により、386アーキテクチャにおけるcas64関数のアセンブリ実装と、それを利用するCコードが、新しいcas64関数のシグネチャに正しく対応し、ビルドおよび実行時の整合性が保たれるようになりました。

関連リンク

参考にした情報源リンク

  • https://golang.org/cl/10803045 (このコミットのChange List)
  • https://golang.org/cl/10909045 (このコミットが参照している、cas64変更の元のChange List)
  • Go言語のソースコード (特にsrc/pkg/runtime/asm_386.ssrc/pkg/runtime/atomic_386.c)
  • Intel 386プロセッサの命令セットリファレンス (特にCMPXCHG8B命令に関する情報)
  • Go言語の呼び出し規約に関する情報 (アセンブリコードの理解に必要)
  • アトミック操作とCASに関する一般的なコンピュータサイエンスの知識