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

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

このコミットは、Go言語の標準ライブラリであるsync/atomicパッケージに、Google Native Client (NaCl) 環境で動作するARMアーキテクチャ(具体的にはARMv7A)向けの低レベルなアトミック操作の実装を追加するものです。これにより、NaCl上でGoプログラムが並行処理を安全かつ効率的に実行できるようになります。追加されるファイルはsrc/pkg/sync/atomic/asm_nacl_arm.sであり、これはARMアセンブリ言語で記述されています。

コミット

sync/atomic: nacl/arm support.

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

https://github.com/golang/go/commit/62af04c0bc86959f1d0da5cdadfd5eec141d4f52

元コミット内容

sync/atomic: nacl/arm support.

LGTM=dave, rsc R=rsc, iant, dave CC=golang-codereviews https://golang.org/cl/110330044

変更の背景

Go言語は、その強力な並行処理機能とクロスプラットフォーム対応が特徴です。sync/atomicパッケージは、複数のゴルーチン(Goの軽量スレッド)が共有メモリ上のデータを安全に操作するためのアトミックなプリミティブを提供します。これらのプリミティブは、競合状態(Race Condition)を防ぎ、データの一貫性を保証するために不可欠です。

Google Native Client (NaCl) は、ウェブブラウザ内でネイティブコードを安全に実行するためのサンドボックス技術です。特に、Portable Native Client (PNaCl) は、異なるCPUアーキテクチャ間で移植可能なバイナリを提供することを目指しています。ARMアーキテクチャは、モバイルデバイスや組み込みシステムで広く採用されており、NaCl環境でGoプログラムを動作させるためには、その特定のアーキテクチャに最適化されたアトミック操作の実装が必要となります。

このコミットが行われた2014年当時、Go言語は様々なプラットフォームへの対応を強化していました。NaCl/ARM環境でGoの並行処理モデルを完全にサポートするためには、sync/atomicパッケージが提供するCompareAndSwapAddSwapLoadStoreといった基本的なアトミック操作を、NaClのサンドボックス制約とARMv7Aの命令セットに適合するように低レベルで実装する必要がありました。このコミットは、そのための基盤となるアセンブリコードを提供することで、GoプログラムがNaCl/ARM環境で信頼性の高い並行処理を行えるようにすることを目的としています。

前提知識の解説

Go言語のsync/atomicパッケージ

Go言語のsync/atomicパッケージは、ミューテックス(sync.Mutex)のようなロック機構を使用せずに、共有変数へのアトミックな操作(不可分な操作)を提供するパッケージです。アトミック操作は、複数のゴルーチンが同時に同じメモリ位置にアクセスしても、その操作が中断されることなく完全に実行されることを保証します。これにより、競合状態を防ぎ、プログラムの正確性とパフォーマンスを向上させることができます。主な操作には、値の比較と交換(CompareAndSwap)、加算(Add)、交換(Swap)、ロード(Load)、ストア(Store)などがあります。

Google Native Client (NaCl)

Google Native Client (NaCl) は、ウェブブラウザ内でC/C++などのネイティブコードを安全に実行するためのオープンソースのサンドボックス技術です。NaClは、セキュリティと移植性を重視しており、実行されるネイティブコードがシステムに危害を加えないように厳格な検証プロセスを経ます。PNaCl (Portable Native Client) は、NaClの進化版であり、特定のCPUアーキテクチャに依存しない中間表現(LLVM IRベース)を使用することで、一度コンパイルすれば様々なアーキテクチャで実行できる「Write Once, Run Anywhere」を実現します。NaCl環境では、セキュリティ上の理由から、通常のシステムコールや低レベルなアセンブリ命令の直接使用が制限されることがあります。

ARMアーキテクチャ (特にARMv7A)

ARM (Advanced RISC Machine) は、モバイルデバイス、組み込みシステム、IoTデバイスなどで広く使用されているRISCベースのCPUアーキテクチャです。ARMv7Aは、ARMアーキテクチャの主要なバージョンの一つで、Cortex-Aシリーズのプロセッサで採用されています。マルチコアプロセッサをサポートしており、複数のコアが同時にメモリにアクセスする環境では、メモリの一貫性を保つためのアトミック操作が非常に重要になります。

アトミック操作とLDREX/STREX命令

アトミック操作は、並行プログラミングにおいて、複数のスレッドやプロセスが共有データにアクセスする際に、その操作が不可分(分割不可能)であることを保証するものです。これにより、データ破損や予期せぬ動作を防ぎます。

ARMv7Aアーキテクチャでは、アトミック操作を実現するためにロード・リンク/ストア・コンディショナル (LL/SC) 命令のペアが提供されています。具体的には、LDREX (Load-Exclusive) と STREX (Store-Exclusive) 命令です。

  • LDREX (Load-Exclusive): 指定されたメモリアドレスから値をロードし、そのアドレスを「排他モニター」に登録します。排他モニターは、そのアドレスに対する他のプロセッサからの書き込みを監視します。
  • STREX (Store-Exclusive): 指定されたメモリアドレスに値をストアしようとします。この際、排他モニターが監視しているアドレスが、LDREX以降に他のプロセッサによって変更されていないことを確認します。
    • 変更がなければストアは成功し、排他モニターはクリアされます。
    • 変更があった場合(他のプロセッサがそのアドレスに書き込んだ場合)、ストアは失敗し、レジスタに失敗を示す値が書き込まれます。

アトミックな「読み込み-変更-書き込み」操作(例: CompareAndSwapやAdd)は、通常、LDREXSTREXをループ内で使用して実装されます。LDREXで値を読み込み、必要な変更を行い、STREXで書き込みを試みます。STREXが失敗した場合は、ループを繰り返して再試行します。これにより、競合が発生しても操作全体がアトミックに実行されることが保証されます。

NaCl環境下でのアセンブリコードの制約

NaClのサンドボックスは、セキュリティと移植性を確保するために、アプリケーションが直接低レベルなハードウェア命令にアクセスすることを厳しく制限します。通常、NaClアプリケーションはC11 Atomics (<stdatomic.h>) やGCCの__sync組み込み関数のような高レベルな抽象化を通じてアトミック操作を行います。これらの高レベルなAPIは、コンパイラ(PNaClの場合はLLVM)によって、NaClの検証器が許可する特定のLLVM組み込み関数や、最終的にLDREX/STREXのような安全な低レベル命令に変換されます。

しかし、Go言語のランタイム自体は、パフォーマンスと正確性を最大限に引き出すために、特定のアーキテクチャ向けに最適化されたアセンブリコードを直接使用することがあります。このコミットは、Goランタイムのsync/atomicパッケージが、NaClのサンドボックス内で安全かつ効率的に動作するために、ARMv7AのLDREX/STREX命令を直接利用するアセンブリコードを導入していることを示しています。これは、GoランタイムがNaClの検証プロセスを通過できるような形で、これらの低レベル命令をラップしていることを意味します。

技術的詳細

このコミットは、Goのsync/atomicパッケージが提供するアトミック操作を、NaCl環境下のARMv7Aアーキテクチャで利用可能にするためのアセンブリコードを実装しています。Goのsync/atomicパッケージは、int32, uint32, int64, uint64, uintptr, Pointer型に対するアトミックなCompareAndSwapAddSwapLoadStore操作を提供します。

Goのアセンブリ言語は、Plan 9アセンブラの構文に基づいています。このコミットで追加されたasm_nacl_arm.sファイルは、これらのアトミック操作をARMv7AのLDREX(Load-Exclusive)とSTREX(Store-Exclusive)命令を使用して実装しています。

ファイル内の各TEXTディレクティブは、Goの関数に対応します。例えば、TEXT ·CompareAndSwapInt32(SB),NOSPLIT,$0は、Goのsync/atomic.CompareAndSwapInt32関数に対応するアセンブリコードの開始を示します。

  • TEXT: Goのアセンブリ関数を定義するためのディレクティブ。
  • ·CompareAndSwapInt32(SB): 関数名。·はパッケージパスと関数名を区切る記号。SBはStatic Baseレジスタで、グローバルシンボルや関数への参照に使われます。
  • NOSPLIT: この関数がスタックフレームを生成しないことを示します。これにより、関数呼び出しのオーバーヘッドが削減されます。アトミック操作のような非常に短い関数でよく使われます。
  • $0: この関数のスタックフレームサイズが0バイトであることを示します。$-4のような負の値は、呼び出し元がスタックに引数をプッシュするが、この関数自体はスタックフレームを必要としないことを示します。

多くの関数は、より汎用的なUint32Uint64バージョンにB(Branch、ジャンプ)命令でリダイレクトされています。これは、符号付き整数と符号なし整数のアトミック操作が、ビットパターンとしては同じ操作であるため、共通の実装を再利用できることを意味します。例えば、CompareAndSwapInt32CompareAndSwapUint32にジャンプします。

特に注目すべきは、LoadUint32StoreUint32の実装です。これらはLDREXSTREX命令を直接使用して、アトミックな読み込みと書き込みを実現しています。

  • LDREX (R1), R2: R1が指すメモリアドレスから排他的に値をロードし、その値をR2に格納します。同時に、排他モニターがこのアドレスを監視し始めます。
  • STREX R2, (R1), R0: R2の値をR1が指すメモリアドレスにストアしようとします。ストアが成功した場合(LDREX以降、他のプロセッサがこのアドレスに書き込んでいない場合)、R0には0がセットされます。失敗した場合(競合が発生した場合)、R0には非ゼロの値がセットされます。
  • CMP $0, R0: R0の値が0と比較されます。
  • BNE load32loop / BNE storeloop: R0が0でなければ(つまりSTREXが失敗した場合)、load32loopまたはstoreloopラベルにジャンプして操作を再試行します。これにより、競合が発生しても最終的にアトミックな操作が成功することが保証されます。
  • MOVW addr+0(FP), R1: 関数引数addr(アドレス)をフレームポインタFPからのオフセットで取得し、R1レジスタにロードします。
  • MOVW val+4(FP), R2: 関数引数val(値)をフレームポインタFPからのオフセットで取得し、R2レジスタにロードします。
  • RET: 関数からリターンします。

このアセンブリコードは、GoのランタイムがNaClのサンドボックス内で、ARMv7Aのハードウェアが提供するアトミック操作機能を最大限に活用し、効率的かつ安全な並行処理を実現するための重要なコンポーネントです。

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

このコミットでは、以下のファイルが新規追加されています。

src/pkg/sync/atomic/asm_nacl_arm.s (109行の追加)

// Copyright 2014 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.

#include "../../../cmd/ld/textflag.h"

// NaCl/ARM atomic operations.
// NaCl/ARM explicitly targets ARMv7A.

TEXT ·CompareAndSwapInt32(SB),NOSPLIT,$0
	B ·CompareAndSwapUint32(SB)

TEXT ·CompareAndSwapUint32(SB),NOSPLIT,$0
	B ·armCompareAndSwapUint32(SB)

TEXT ·CompareAndSwapUintptr(SB),NOSPLIT,$0
	B ·CompareAndSwapUint32(SB)

TEXT ·CompareAndSwapPointer(SB),NOSPLIT,$0
	B ·CompareAndSwapUint32(SB)

TEXT ·AddInt32(SB),NOSPLIT,$0
	B ·AddUint32(SB)

TEXT ·AddUint32(SB),NOSPLIT,$0
	B ·armAddUint32(SB)

TEXT ·AddUintptr(SB),NOSPLIT,$0
	B ·AddUint32(SB)

TEXT ·SwapInt32(SB),NOSPLIT,$0
	B ·SwapUint32(SB)

TEXT ·SwapUint32(SB),NOSPLIT,$0
	B ·armSwapUint32(SB)

TEXT ·SwapUintptr(SB),NOSPLIT,$0
	B ·SwapUint32(SB)

TEXT ·SwapPointer(SB),NOSPLIT,$0
	B ·SwapUint32(SB)

TEXT ·CompareAndSwapInt64(SB),NOSPLIT,$0
	B ·CompareAndSwapUint64(SB)

TEXT ·CompareAndSwapUint64(SB),NOSPLIT,$-4
	B ·armCompareAndSwapUint64(SB)

TEXT ·AddInt64(SB),NOSPLIT,$0
	B ·addUint64(SB)

TEXT ·AddUint64(SB),NOSPLIT,$0
	B ·addUint64(SB)

TEXT ·SwapInt64(SB),NOSPLIT,$0
	B ·swapUint64(SB)

TEXT ·SwapUint64(SB),NOSPLIT,$0
	B ·swapUint64(SB)

TEXT ·LoadInt32(SB),NOSPLIT,$0
	B ·LoadUint32(SB)

TEXT ·LoadUint32(SB),NOSPLIT,$0-8
	MOVW addr+0(FP), R1
load32loop:
	LDREX (R1), R2		// loads R2
	STREX R2, (R1), R0	// stores R2
	CMP $0, R0
	BNE load32loop
	MOVW R2, val+4(FP)
	RET

TEXT ·LoadInt64(SB),NOSPLIT,$0
	B ·loadUint64(SB)

TEXT ·LoadUint64(SB),NOSPLIT,$0
	B ·loadUint64(SB)

TEXT ·LoadUintptr(SB),NOSPLIT,$0
	B ·LoadUint32(SB)

TEXT ·LoadPointer(SB),NOSPLIT,$0
	B ·LoadUint32(SB)

TEXT ·StoreInt32(SB),NOSPLIT,$0
	B ·StoreUint32(SB)

TEXT ·StoreUint32(SB),NOSPLIT,$0-8
	MOVW addr+0(FP), R1
	MOVW val+4(FP), R2
storeloop:
	LDREX (R1), R4		// loads R4
	STREX R2, (R1), R0	// stores R2
	CMP $0, R0
	BNE storeloop
	RET

TEXT ·StoreInt64(SB),NOSPLIT,$0
	B ·storeUint64(SB)

TEXT ·StoreUint64(SB),NOSPLIT,$0
	B ·storeUint64(SB)

TEXT ·StoreUintptr(SB),NOSPLIT,$0
	B ·StoreUint32(SB)

TEXT ·StorePointer(SB),NOSPLIT,$0
	B ·StoreUint32(SB)

コアとなるコードの解説

このアセンブリファイルは、Goのsync/atomicパッケージが提供する様々なアトミック操作のARMv7A (NaCl) 向け実装の入り口を提供します。

ファイルの冒頭では、Goアセンブリの標準的なヘッダと、cmd/ld/textflag.hのインクルードがあります。これは、NOSPLITなどのテキストフラグを定義するために必要です。コメントで「NaCl/ARM explicitly targets ARMv7A」と明記されており、このコードがARMv7A命令セットに特化していることがわかります。

コードの大部分は、Goのsync/atomicパッケージの公開関数(例: CompareAndSwapInt32)から、実際の処理を行う内部のARM固有のアセンブリ関数(例: armCompareAndSwapUint32)へのジャンプ(B命令)で構成されています。

  • 型ごとのリダイレクト:

    • Int32, Uintptr, Pointer型のアトミック操作は、対応するUint32型のアトミック操作にリダイレクトされています。これは、32ビット幅のデータに対するアトミック操作は、符号の有無に関わらず同じ低レベルな命令で処理できるためです。
    • 同様に、Int64型のアトミック操作は、対応するUint64型のアトミック操作にリダイレクトされています。
    • これにより、コードの重複を避け、共通の低レベル実装を再利用しています。
  • LoadUint32関数の詳細:

    TEXT ·LoadUint32(SB),NOSPLIT,$0-8
    	MOVW addr+0(FP), R1
    load32loop:
    	LDREX (R1), R2		// loads R2
    	STREX R2, (R1), R0	// stores R2
    	CMP $0, R0
    	BNE load32loop
    	MOVW R2, val+4(FP)
    	RET
    
    1. MOVW addr+0(FP), R1: ロードするアドレス(addr)を関数の第一引数から取得し、レジスタR1に格納します。FPはフレームポインタで、addr+0(FP)はスタック上の引数addrへの参照です。
    2. load32loop:: アトミックなロード操作のループ開始点。
    3. LDREX (R1), R2: R1が指すメモリ位置から値を排他的にロードし、R2に格納します。この操作により、排他モニターがこのメモリ位置を監視し始めます。
    4. STREX R2, (R1), R0: R2の値をR1が指すメモリ位置にストアしようとします。このストアは、LDREX以降に他のプロセッサがこのメモリ位置に書き込んでいない場合にのみ成功します。成功した場合、R0は0になります。失敗した場合(競合が発生した場合)、R0は非ゼロになります。
    5. CMP $0, R0: STREX命令の結果(R0の値)が0と比較されます。
    6. BNE load32loop: R0が0でなければ(つまりSTREXが失敗した場合)、load32loopラベルにジャンプして操作を再試行します。これにより、競合が発生しても最終的にアトミックなロードが成功することが保証されます。
    7. MOVW R2, val+4(FP): 成功したロード結果(R2に格納されている値)を、関数の戻り値としてスタック上のval(第二引数、実際には戻り値の格納場所)に格納します。
    8. RET: 関数からリターンします。
  • StoreUint32関数の詳細:

    TEXT ·StoreUint32(SB),NOSPLIT,$0-8
    	MOVW addr+0(FP), R1
    	MOVW val+4(FP), R2
    storeloop:
    	LDREX (R1), R4		// loads R4
    	STREX R2, (R1), R0	// stores R2
    	CMP $0, R0
    	BNE storeloop
    	RET
    
    1. MOVW addr+0(FP), R1: ストアするアドレス(addr)をR1に格納します。
    2. MOVW val+4(FP), R2: ストアする値(val)をR2に格納します。
    3. storeloop:: アトミックなストア操作のループ開始点。
    4. LDREX (R1), R4: R1が指すメモリ位置から値を排他的にロードし、R4に格納します。このロードは、STREXが成功するための前提条件として排他モニターをセットするために行われます。ロードされた値自体は、このストア操作では直接使用されません。
    5. STREX R2, (R1), R0: R2の値をR1が指すメモリ位置にストアしようとします。LDREXと同様に、成功すればR0は0、失敗すれば非ゼロです。
    6. CMP $0, R0: STREX命令の結果をチェックします。
    7. BNE storeloop: STREXが失敗した場合、storeloopラベルにジャンプして操作を再試行します。
    8. RET: 関数からリターンします。

これらの実装は、ARMv7AのLL/SC(Load-Linked/Store-Conditional)メカニズムを直接利用することで、Goのsync/atomicパッケージがNaCl環境下でも効率的かつ正確に動作することを保証しています。

関連リンク

参考にした情報源リンク

  • ARMv7-A Architecture Reference Manual (LDREX/STREX instructions)
  • Google Native Client (NaCl) Documentation
  • Go sync/atomic package documentation
  • C11 Atomics (<stdatomic.h>)
  • GCC __sync Builtins
  • LLVM Project Documentation (PNaCl toolchain)
  • Stack Overflow discussions on ARM atomics and NaCl
  • Wikipedia: Load-link/store-conditionalI have generated the detailed explanation in Markdown format, following all the specified sections and incorporating the information gathered from the web search. I will now output it to standard output.
# [インデックス 19711] ファイルの概要

このコミットは、Go言語の標準ライブラリである`sync/atomic`パッケージに、Google Native Client (NaCl) 環境で動作するARMアーキテクチャ(具体的にはARMv7A)向けの低レベルなアトミック操作の実装を追加するものです。これにより、NaCl上でGoプログラムが並行処理を安全かつ効率的に実行できるようになります。追加されるファイルは`src/pkg/sync/atomic/asm_nacl_arm.s`であり、これはARMアセンブリ言語で記述されています。

## コミット

sync/atomic: nacl/arm support.

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

[https://github.com/golang/go/commit/62af04c0bc86959f1d0da5cdadfd5eec141d4f52](https://github.com/golang/go/commit/62af04c0bc86959f1d0da5cdadfd5eec141d4f52)

## 元コミット内容

sync/atomic: nacl/arm support.

LGTM=dave, rsc
R=rsc, iant, dave
CC=golang-codereviews
https://golang.org/cl/110330044

## 変更の背景

Go言語は、その強力な並行処理機能とクロスプラットフォーム対応が特徴です。`sync/atomic`パッケージは、複数のゴルーチン(Goの軽量スレッド)が共有メモリ上のデータを安全に操作するためのアトミックなプリミティブを提供します。これらのプリミティブは、競合状態(Race Condition)を防ぎ、データの一貫性を保証するために不可欠です。

Google Native Client (NaCl) は、ウェブブラウザ内でネイティブコードを安全に実行するためのサンドボックス技術です。特に、Portable Native Client (PNaCl) は、異なるCPUアーキテクチャ間で移植可能なバイナリを提供することを目指しています。ARMアーキテクチャは、モバイルデバイスや組み込みシステムで広く採用されており、NaCl環境でGoプログラムを動作させるためには、その特定のアーキテクチャに最適化されたアトミック操作の実装が必要となります。

このコミットが行われた2014年当時、Go言語は様々なプラットフォームへの対応を強化していました。NaCl/ARM環境でGoの並行処理モデルを完全にサポートするためには、`sync/atomic`パッケージが提供する`CompareAndSwap`、`Add`、`Swap`、`Load`、`Store`といった基本的なアトミック操作を、NaClのサンドボックス制約とARMv7Aの命令セットに適合するように低レベルで実装する必要がありました。このコミットは、そのための基盤となるアセンブリコードを提供することで、GoプログラムがNaCl/ARM環境で信頼性の高い並行処理を行えるようにすることを目的としています。

## 前提知識の解説

### Go言語の`sync/atomic`パッケージ

Go言語の`sync/atomic`パッケージは、ミューテックス(`sync.Mutex`)のようなロック機構を使用せずに、共有変数へのアトミックな操作(不可分な操作)を提供するパッケージです。アトミック操作は、複数のゴルーチンが同時に同じメモリ位置にアクセスしても、その操作が中断されることなく完全に実行されることを保証します。これにより、競合状態を防ぎ、プログラムの正確性とパフォーマンスを向上させることができます。主な操作には、値の比較と交換(CompareAndSwap)、加算(Add)、交換(Swap)、ロード(Load)、ストア(Store)などがあります。

### Google Native Client (NaCl)

Google Native Client (NaCl) は、ウェブブラウザ内でC/C++などのネイティブコードを安全に実行するためのオープンソースのサンドボックス技術です。NaClは、セキュリティと移植性を重視しており、実行されるネイティブコードがシステムに危害を加えないように厳格な検証プロセスを経ます。PNaCl (Portable Native Client) は、NaClの進化版であり、特定のCPUアーキテクチャに依存しない中間表現(LLVM IRベース)を使用することで、一度コンパイルすれば様々なアーキテクチャで実行できる「Write Once, Run Anywhere」を実現します。NaCl環境では、セキュリティ上の理由から、通常のシステムコールや低レベルなアセンブリ命令の直接使用が制限されることがあります。

### ARMアーキテクチャ (特にARMv7A)

ARM (Advanced RISC Machine) は、モバイルデバイス、組み込みシステム、IoTデバイスなどで広く使用されているRISCベースのCPUアーキテクチャです。ARMv7Aは、ARMアーキテクチャの主要なバージョンの一つで、Cortex-Aシリーズのプロセッサで採用されています。マルチコアプロセッサをサポートしており、複数のコアが同時にメモリにアクセスする環境では、メモリの一貫性を保つためのアトミック操作が非常に重要になります。

### アトミック操作とLDREX/STREX命令

アトミック操作は、並行プログラミングにおいて、複数のスレッドやプロセスが共有データにアクセスする際に、その操作が不可分(分割不可能)であることを保証するものです。これにより、データ破損や予期せぬ動作を防ぎます。

ARMv7Aアーキテクチャでは、アトミック操作を実現するために**ロード・リンク/ストア・コンディショナル (LL/SC)** 命令のペアが提供されています。具体的には、`LDREX` (Load-Exclusive) と `STREX` (Store-Exclusive) 命令です。

*   **`LDREX` (Load-Exclusive)**: 指定されたメモリアドレスから値をロードし、そのアドレスを「排他モニター」に登録します。排他モニターは、そのアドレスに対する他のプロセッサからの書き込みを監視します。
*   **`STREX` (Store-Exclusive)**: 指定されたメモリアドレスに値をストアしようとします。この際、排他モニターが監視しているアドレスが、`LDREX`以降に他のプロセッサによって変更されていないことを確認します。
    *   変更がなければストアは成功し、排他モニターはクリアされます。
    *   変更があった場合(他のプロセッサがそのアドレスに書き込んだ場合)、ストアは失敗し、レジスタに失敗を示す値が書き込まれます。

アトミックな「読み込み-変更-書き込み」操作(例: CompareAndSwapやAdd)は、通常、`LDREX`と`STREX`をループ内で使用して実装されます。`LDREX`で値を読み込み、必要な変更を行い、`STREX`で書き込みを試みます。`STREX`が失敗した場合は、ループを繰り返して再試行します。これにより、競合が発生しても操作全体がアトミックに実行されることが保証されます。

### NaCl環境下でのアセンブリコードの制約

NaClのサンドボックスは、セキュリティと移植性を確保するために、アプリケーションが直接低レベルなハードウェア命令にアクセスすることを厳しく制限します。通常、NaClアプリケーションはC11 Atomics (`<stdatomic.h>`) やGCCの`__sync`組み込み関数のような高レベルな抽象化を通じてアトミック操作を行います。これらの高レベルなAPIは、コンパイラ(PNaClの場合はLLVM)によって、NaClの検証器が許可する特定のLLVM組み込み関数や、最終的に`LDREX`/`STREX`のような安全な低レベル命令に変換されます。

しかし、Go言語のランタイム自体は、パフォーマンスと正確性を最大限に引き出すために、特定のアーキテクチャ向けに最適化されたアセンブリコードを直接使用することがあります。このコミットは、Goランタイムの`sync/atomic`パッケージが、NaClのサンドボックス内で安全かつ効率的に動作するために、ARMv7Aの`LDREX`/`STREX`命令を直接利用するアセンブリコードを導入していることを示しています。これは、GoランタイムがNaClの検証プロセスを通過できるような形で、これらの低レベル命令をラップしていることを意味します。

## 技術的詳細

このコミットは、Goの`sync/atomic`パッケージが提供するアトミック操作を、NaCl環境下のARMv7Aアーキテクチャで利用可能にするためのアセンブリコードを実装しています。Goの`sync/atomic`パッケージは、`int32`, `uint32`, `int64`, `uint64`, `uintptr`, `Pointer`型に対するアトミックな`CompareAndSwap`、`Add`、`Swap`、`Load`、`Store`操作を提供します。

Goのアセンブリ言語は、Plan 9アセンブラの構文に基づいています。このコミットで追加された`asm_nacl_arm.s`ファイルは、これらのアトミック操作をARMv7Aの`LDREX`(Load-Exclusive)と`STREX`(Store-Exclusive)命令を使用して実装しています。

ファイル内の各`TEXT`ディレクティブは、Goの関数に対応します。例えば、`TEXT ·CompareAndSwapInt32(SB),NOSPLIT,$0`は、Goの`sync/atomic.CompareAndSwapInt32`関数に対応するアセンブリコードの開始を示します。

*   **`TEXT`**: Goのアセンブリ関数を定義するためのディレクティブ。
*   **`·CompareAndSwapInt32(SB)`**: 関数名。`·`はパッケージパスと関数名を区切る記号。`SB`はStatic Baseレジスタで、グローバルシンボルや関数への参照に使われます。
*   **`NOSPLIT`**: この関数がスタックフレームを生成しないことを示します。これにより、関数呼び出しのオーバーヘッドが削減されます。アトミック操作のような非常に短い関数でよく使われます。
*   **`$0`**: この関数のスタックフレームサイズが0バイトであることを示します。`$-4`のような負の値は、呼び出し元がスタックに引数をプッシュするが、この関数自体はスタックフレームを必要としないことを示します。

多くの関数は、より汎用的な`Uint32`や`Uint64`バージョンに`B`(Branch、ジャンプ)命令でリダイレクトされています。これは、符号付き整数と符号なし整数のアトミック操作が、ビットパターンとしては同じ操作であるため、共通の実装を再利用できることを意味します。例えば、`CompareAndSwapInt32`は`CompareAndSwapUint32`にジャンプします。

特に注目すべきは、`LoadUint32`と`StoreUint32`の実装です。これらは`LDREX`と`STREX`命令を直接使用して、アトミックな読み込みと書き込みを実現しています。

*   **`LDREX (R1), R2`**: `R1`が指すメモリアドレスから排他的に値をロードし、`R2`に格納します。同時に、排他モニターがこのメモリ位置を監視し始めます。
*   **`STREX R2, (R1), R0`**: `R2`の値を`R1`が指すメモリアドレスにストアしようとします。このストアは、`LDREX`以降に他のプロセッサがこのメモリ位置に書き込んでいない場合にのみ成功します。成功した場合、`R0`は0になります。失敗した場合(競合が発生した場合)、`R0`は非ゼロになります。
*   **`CMP $0, R0`**: `STREX`命令の結果(`R0`の値)が0と比較されます。
*   **`BNE load32loop` / `BNE storeloop`**: `R0`が0でなければ(つまり`STREX`が失敗した場合)、`load32loop`または`storeloop`ラベルにジャンプして操作を再試行します。これにより、競合が発生しても最終的にアトミックな操作が成功することが保証されます。
*   **`MOVW addr+0(FP), R1`**: 関数引数`addr`(アドレス)をフレームポインタ`FP`からのオフセットで取得し、`R1`レジスタにロードします。
*   **`MOVW val+4(FP), R2`**: 関数引数`val`(値)をフレームポインタ`FP`からのオフセットで取得し、`R2`レジスタにロードします。
*   **`RET`**: 関数からリターンします。

このアセンブリコードは、GoのランタイムがNaClのサンドボックス内で、ARMv7Aのハードウェアが提供するアトミック操作機能を最大限に活用し、効率的かつ安全な並行処理を実現するための重要なコンポーネントです。

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

このコミットでは、以下のファイルが新規追加されています。

`src/pkg/sync/atomic/asm_nacl_arm.s` (109行の追加)

```assembly
// Copyright 2014 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.

#include "../../../cmd/ld/textflag.h"

// NaCl/ARM atomic operations.
// NaCl/ARM explicitly targets ARMv7A.

TEXT ·CompareAndSwapInt32(SB),NOSPLIT,$0
	B ·CompareAndSwapUint32(SB)

TEXT ·CompareAndSwapUint32(SB),NOSPLIT,$0
	B ·armCompareAndSwapUint32(SB)

TEXT ·CompareAndSwapUintptr(SB),NOSPLIT,$0
	B ·CompareAndSwapUint32(SB)

TEXT ·CompareAndSwapPointer(SB),NOSPLIT,$0
	B ·CompareAndSwapUint32(SB)

TEXT ·AddInt32(SB),NOSPLIT,$0
	B ·AddUint32(SB)

TEXT ·AddUint32(SB),NOSPLIT,$0
	B ·armAddUint32(SB)

TEXT ·AddUintptr(SB),NOSPLIT,$0
	B ·AddUint32(SB)

TEXT ·SwapInt32(SB),NOSPLIT,$0
	B ·SwapUint32(SB)

TEXT ·SwapUint32(SB),NOSPLIT,$0
	B ·armSwapUint32(SB)

TEXT ·SwapUintptr(SB),NOSPLIT,$0
	B ·SwapUint32(SB)

TEXT ·SwapPointer(SB),NOSPLIT,$0
	B ·SwapUint32(SB)

TEXT ·CompareAndSwapInt64(SB),NOSPLIT,$0
	B ·CompareAndSwapUint64(SB)

TEXT ·CompareAndSwapUint64(SB),NOSPLIT,$-4
	B ·armCompareAndSwapUint64(SB)

TEXT ·AddInt64(SB),NOSPLIT,$0
	B ·addUint64(SB)

TEXT ·AddUint64(SB),NOSPLIT,$0
	B ·addUint64(SB)

TEXT ·SwapInt64(SB),NOSPLIT,$0
	B ·swapUint64(SB)

TEXT ·SwapUint64(SB),NOSPLIT,$0
	B ·swapUint64(SB)

TEXT ·LoadInt32(SB),NOSPLIT,$0
	B ·LoadUint32(SB)

TEXT ·LoadUint32(SB),NOSPLIT,$0-8
	MOVW addr+0(FP), R1
load32loop:
	LDREX (R1), R2		// loads R2
	STREX R2, (R1), R0	// stores R2
	CMP $0, R0
	BNE load32loop
	MOVW R2, val+4(FP)
	RET

TEXT ·LoadInt64(SB),NOSPLIT,$0
	B ·loadUint64(SB)

TEXT ·LoadUint64(SB),NOSPLIT,$0
	B ·loadUint64(SB)

TEXT ·LoadUintptr(SB),NOSPLIT,$0
	B ·LoadUint32(SB)

TEXT ·LoadPointer(SB),NOSPLIT,$0
	B ·LoadUint32(SB)

TEXT ·StoreInt32(SB),NOSPLIT,$0
	B ·StoreUint32(SB)

TEXT ·StoreUint32(SB),NOSPLIT,$0-8
	MOVW addr+0(FP), R1
	MOVW val+4(FP), R2
storeloop:
	LDREX (R1), R4		// loads R4
	STREX R2, (R1), R0	// stores R2
	CMP $0, R0
	BNE storeloop
	RET

TEXT ·StoreInt64(SB),NOSPLIT,$0
	B ·storeUint64(SB)

TEXT ·StoreUint64(SB),NOSPLIT,$0
	B ·storeUint64(SB)

TEXT ·StoreUintptr(SB),NOSPLIT,$0
	B ·StoreUint32(SB)

TEXT ·StorePointer(SB),NOSPLIT,$0
	B ·StoreUint32(SB)

コアとなるコードの解説

このアセンブリファイルは、Goのsync/atomicパッケージが提供する様々なアトミック操作のARMv7A (NaCl) 向け実装の入り口を提供します。

ファイルの冒頭では、Goアセンブリの標準的なヘッダと、cmd/ld/textflag.hのインクルードがあります。これは、NOSPLITなどのテキストフラグを定義するために必要です。コメントで「NaCl/ARM explicitly targets ARMv7A」と明記されており、このコードがARMv7A命令セットに特化していることがわかります。

コードの大部分は、Goのsync/atomicパッケージの公開関数(例: CompareAndSwapInt32)から、実際の処理を行う内部のARM固有のアセンブリ関数(例: armCompareAndSwapUint32)へのジャンプ(B命令)で構成されています。

  • 型ごとのリダイレクト:

    • Int32, Uintptr, Pointer型のアトミック操作は、対応するUint32型のアトミック操作にリダイレクトされています。これは、32ビット幅のデータに対するアトミック操作は、符号の有無に関わらず同じ低レベルな命令で処理できるためです。
    • 同様に、Int64型のアトミック操作は、対応するUint64型のアトミック操作にリダイレクトされています。
    • これにより、コードの重複を避け、共通の低レベル実装を再利用しています。
  • LoadUint32関数の詳細:

    TEXT ·LoadUint32(SB),NOSPLIT,$0-8
    	MOVW addr+0(FP), R1
    load32loop:
    	LDREX (R1), R2		// loads R2
    	STREX R2, (R1), R0	// stores R2
    	CMP $0, R0
    	BNE load32loop
    	MOVW R2, val+4(FP)
    	RET
    
    1. MOVW addr+0(FP), R1: ロードするアドレス(addr)を関数の第一引数から取得し、レジスタR1に格納します。FPはフレームポインタで、addr+0(FP)はスタック上の引数addrへの参照です。
    2. load32loop:: アトミックなロード操作のループ開始点。
    3. LDREX (R1), R2: R1が指すメモリ位置から値を排他的にロードし、R2に格納します。この操作により、排他モニターがこのメモリ位置を監視し始めます。
    4. STREX R2, (R1), R0: R2の値をR1が指すメモリ位置にストアしようとします。このストアは、LDREX以降に他のプロセッサがこのメモリ位置に書き込んでいない場合にのみ成功します。成功した場合、R0は0になります。失敗した場合(競合が発生した場合)、R0は非ゼロになります。
    5. CMP $0, R0: STREX命令の結果(R0の値)が0と比較されます。
    6. BNE load32loop: R0が0でなければ(つまりSTREXが失敗した場合)、load32loopラベルにジャンプして操作を再試行します。これにより、競合が発生しても最終的にアトミックなロードが成功することが保証されます。
    7. MOVW R2, val+4(FP): 成功したロード結果(R2に格納されている値)を、関数の戻り値としてスタック上のval(第二引数、実際には戻り値の格納場所)に格納します。
    8. RET: 関数からリターンします。
  • StoreUint32関数の詳細:

    TEXT ·StoreUint32(SB),NOSPLIT,$0-8
    	MOVW addr+0(FP), R1
    	MOVW val+4(FP), R2
    storeloop:
    	LDREX (R1), R4		// loads R4
    	STREX R2, (R1), R0	// stores R2
    	CMP $0, R0
    	BNE storeloop
    	RET
    
    1. MOVW addr+0(FP), R1: ストアするアドレス(addr)をR1に格納します。
    2. MOVW val+4(FP), R2: ストアする値(val)をR2に格納します。
    3. storeloop:: アトミックなストア操作のループ開始点。
    4. LDREX (R1), R4: R1が指すメモリ位置から値を排他的にロードし、R4に格納します。このロードは、STREXが成功するための前提条件として排他モニターをセットするために行われます。ロードされた値自体は、このストア操作では直接使用されません。
    5. STREX R2, (R1), R0: R2の値をR1が指すメモリ位置にストアしようとします。LDREXと同様に、成功すればR0は0、失敗すれば非ゼロです。
    6. CMP $0, R0: STREX命令の結果をチェックします。
    7. BNE storeloop: STREXが失敗した場合、storeloopラベルにジャンプして操作を再試行します。
    8. RET: 関数からリターンします。

これらの実装は、ARMv7AのLL/SC(Load-Linked/Store-Conditional)メカニズムを直接利用することで、Goのsync/atomicパッケージがNaCl環境下でも効率的かつ正確に動作することを保証しています。

関連リンク

参考にした情報源リンク

  • ARMv7-A Architecture Reference Manual (LDREX/STREX instructions)
  • Google Native Client (NaCl) Documentation
  • Go sync/atomic package documentation
  • C11 Atomics (<stdatomic.h>)
  • GCC __sync Builtins
  • LLVM Project Documentation (PNaCl toolchain)
  • Stack Overflow discussions on ARM atomics and NaCl
  • Wikipedia: Load-link/store-conditional