[インデックス 18641] ファイルの概要
このコミットは、Go言語がGoogle Native Client (NaCl) をサポートするための大規模な変更セットの第一弾です。主に、Go 1.2ベースのNaClブランチからの機械的な変更を取り込んでおり、コンパイラ、ランタイム、システムコール、標準ライブラリなど、Goの様々なコンポーネントにNaCl固有のサポートを追加しています。また、amd64p32
アーキテクチャへの対応も含まれています。
コミット
- コミットハッシュ:
7c8280c9efcd24b882e441d359b6880c1a456ad8
- Author: Dave Cheney dave@cheney.net
- Date: Tue Feb 25 09:47:42 2014 -0500
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7c8280c9efcd24b882e441d359b6880c1a456ad8
元コミット内容
all: merge NaCl branch (part 1)
See golang.org/s/go13nacl for design overview.
This CL is the mostly mechanical changes from rsc's Go 1.2 based NaCl branch, specifically 39cb35750369 to 500771b477cf from https://code.google.com/r/rsc-go13nacl. This CL does not include working NaCl support, there are probably two or three more large merges to come.
CL 15750044 is not included as it involves more invasive changes to the linker which will need to be merged separately.
The exact change lists included are
15050047: syscall: support for Native Client
15360044: syscall: unzip implementation for Native Client
15370044: syscall: Native Client SRPC implementation
15400047: cmd/dist, cmd/go, go/build, test: support for Native Client
15410048: runtime: support for Native Client
15410049: syscall: file descriptor table for Native Client
15410050: syscall: in-memory file system for Native Client
15440048: all: update +build lines for Native Client port
15540045: cmd/6g, cmd/8g, cmd/gc: support for Native Client
15570045: os: support for Native Client
15680044: crypto/..., hash/crc32, reflect, sync/atomic: support for amd64p32
15690044: net: support for Native Client
15690048: runtime: support for fake time like on Go Playground
15690051: build: disable various tests on Native Client
LGTM=rsc
R=rsc
CC=golang-codereviews
https://golang.org/cl/68150047
変更の背景
このコミットの主な背景は、Go言語をGoogle Native Client (NaCl) プラットフォームで動作させるためのサポートを追加することです。NaClは、ウェブブラウザ内でネイティブコードを安全に実行するためのサンドボックス技術であり、Goプログラムをウェブアプリケーションの一部として実行できるようにすることを目指していました。
Go言語は元々、様々なOSやアーキテクチャをサポートするように設計されていますが、NaClのような特殊なサンドボックス環境への対応は、通常のOSとは異なるシステムコール、メモリ管理、ファイルシステムなどの制約を考慮する必要があります。このコミットは、そのための基盤となる変更をGoのツールチェイン、ランタイム、標準ライブラリに導入する「パート1」として位置づけられています。
特に、amd64p32
アーキテクチャのサポートも含まれており、これは64ビットCPU上で32ビットポインタを使用する特殊なモードで、NaCl環境での効率的な実行を目的としています。
前提知識の解説
Google Native Client (NaCl) と Portable Native Client (PNaCl)
Google Native Client (NaCl) は、ウェブブラウザ内でネイティブコード(C/C++など)を安全に実行するためのサンドボックス技術です。これにより、ウェブアプリケーションは、JavaScriptのパフォーマンス限界を超えて、CPUに近い速度で計算集約的なタスクを実行できるようになります。NaClは、セキュリティモデルとして、コードの検証、メモリ分離、システムコール制限などを採用しています。
Portable Native Client (PNaCl) は、NaClの進化版であり、特定のCPUアーキテクチャに依存しない中間表現(LLVM IRベース)を使用します。これにより、開発者は一度コンパイルすれば、PNaClランタイムがサポートする任意のアーキテクチャ(x86-32, x86-64, ARMなど)で実行可能なバイナリを生成できます。ブラウザは、PNaClモジュールをダウンロードし、実行時にターゲットアーキテクチャのネイティブコードにJITコンパイルします。
Go言語がNaClをサポートするということは、Goで書かれたアプリケーションがウェブブラウザのサンドボックス内で、ネイティブに近いパフォーマンスで動作する可能性を開くものでした。
amd64p32
アーキテクチャ
amd64p32
は、64ビットのAMD64 (x86-64) 命令セットを使用しながら、ポインタサイズを32ビットに制限する特殊な実行モードです。これは、特にメモリ使用量を削減し、キャッシュ効率を向上させるために設計されました。
通常の64ビット環境ではポインタは8バイト(64ビット)ですが、amd64p32
では4バイト(32ビット)になります。これにより、データ構造内のポインタが占めるメモリが半分になり、全体的なメモリフットプリントが小さくなります。これは、特に大量のポインタを使用するアプリケーションや、メモリが限られた環境(例えば、ウェブブラウザのサンドボックス内)で有利に働きます。
NaCl環境では、メモリの割り当てやアクセスに厳しい制限があるため、amd64p32
のようなメモリ効率の良いアーキテクチャは非常に重要です。Go言語のランタイムやコンパイラがこのモードをサポートすることは、NaCl上でのGoプログラムの実行効率を向上させる上で不可欠な要素となります。
Go言語のビルドタグ (+build
)
Go言語のソースファイルには、+build
ディレクティブを使用してビルドタグを記述することができます。これは、特定のOS、アーキテクチャ、またはカスタムタグに基づいて、そのファイルをビルドに含めるかどうかを制御するために使用されます。例えば、// +build linux,amd64
と書かれたファイルは、LinuxかつAMD64アーキテクチャの場合にのみビルドに含まれます。
このコミットでは、+build nacl
や+build amd64p32
といった新しいタグが導入され、NaCl固有のコードやamd64p32
固有のコードをGoのビルドシステムが適切に認識し、コンパイルできるように変更されています。
技術的詳細
このコミットは、Go言語をNaCl環境に移植するための多岐にわたる変更を含んでいます。以下に、コミットメッセージに記載されている主要な変更リスト(CLs)に基づいて技術的な詳細を解説します。
-
15050047: syscall: support for Native Client
:- Goのシステムコール層にNaCl固有のシステムコールインターフェースを追加します。NaClはホストOSのシステムコールを直接呼び出すのではなく、独自のIPC (Inter-Process Communication) メカニズムであるSRPC (Simple RPC) を介して、ブラウザのプロセスやNaClランタイムと通信します。このCLは、Goの
syscall
パッケージがNaClのSRPCを介して基本的なOSサービス(ファイル操作、ネットワークなど)にアクセスできるようにするための基盤を構築します。
- Goのシステムコール層にNaCl固有のシステムコールインターフェースを追加します。NaClはホストOSのシステムコールを直接呼び出すのではなく、独自のIPC (Inter-Process Communication) メカニズムであるSRPC (Simple RPC) を介して、ブラウザのプロセスやNaClランタイムと通信します。このCLは、Goの
-
15360044: syscall: unzip implementation for Native Client
:- NaCl環境では、実行可能ファイルが
.nexe
(Native Client Executable)形式で提供されます。これは通常、複数のアーキテクチャ(x86-32, x86-64, ARM)のコードを含む「ファットバイナリ」として配布されます。このCLは、Goのランタイムが.nexe
ファイル内の適切なアーキテクチャのコードを「解凍」または選択して実行するためのメカニズムをsyscall
パッケージ内に実装します。
- NaCl環境では、実行可能ファイルが
-
15370044: syscall: Native Client SRPC implementation
:- NaClのSRPC (Simple RPC) は、NaClモジュールがブラウザやホストシステムと通信するための主要な手段です。このCLは、Goの
syscall
パッケージ内にSRPCクライアントの実装を追加し、GoプログラムがNaClの提供するサービス(例えば、ファイルシステムアクセス、ネットワークソケット操作など)を呼び出せるようにします。これは、Goの標準ライブラリがNaCl上で機能するために不可欠です。
- NaClのSRPC (Simple RPC) は、NaClモジュールがブラウザやホストシステムと通信するための主要な手段です。このCLは、Goの
-
15400047: cmd/dist, cmd/go, go/build, test: support for Native Client
:- Goのビルドツールチェイン(
cmd/dist
,cmd/go
)とビルドシステム(go/build
パッケージ)にNaClのサポートを追加します。これには、新しいGOOS=nacl
とGOARCH=amd64p32
(または他のNaClサポートアーキテクチャ)の組み合わせを認識し、それに応じたクロスコンパイルを可能にする変更が含まれます。また、テストフレームワークもNaCl環境で動作するように調整されます。
- Goのビルドツールチェイン(
-
15410048: runtime: support for Native Client
:- Goランタイムは、ガベージコレクション、スケジューラ、メモリ管理など、Goプログラムの実行を支える中核部分です。このCLは、NaClのサンドボックス環境の制約(例えば、直接的なメモリマップ操作の制限、特定のシステムコールへのアクセス不可など)に対応するために、ランタイムの低レベルな部分を変更します。これには、NaCl固有のメモリ割り当て、スレッド管理、シグナルハンドリングの調整が含まれます。
-
15410049: syscall: file descriptor table for Native Client
:- NaClは、ホストOSのファイルディスクリプタを直接使用するのではなく、独自のファイルディスクリプタ管理システムを持っています。このCLは、Goの
syscall
パッケージ内にNaClのファイルディスクリプタテーブルを管理するロジックを導入し、GoプログラムがNaClのファイルI/O操作を透過的に実行できるようにします。
- NaClは、ホストOSのファイルディスクリプタを直接使用するのではなく、独自のファイルディスクリプタ管理システムを持っています。このCLは、Goの
-
15410050: syscall: in-memory file system for Native Client
:- NaCl環境では、通常のファイルシステムアクセスが制限されるか、または存在しない場合があります。このCLは、Goの
syscall
パッケージ内にインメモリファイルシステム(またはそれに類する抽象化)のサポートを追加し、Goプログラムがファイル操作を必要とする場合に、NaClのサンドボックス内で動作できるようにします。これは、Goの標準ライブラリがファイルI/Oに依存しているため重要です。
- NaCl環境では、通常のファイルシステムアクセスが制限されるか、または存在しない場合があります。このCLは、Goの
-
15440048: all: update +build lines for Native Client port
:- Goのソースコード全体にわたって、
+build nacl
タグを追加または更新し、NaCl固有のコードパスや、NaCl環境で無効化すべきコードパスを適切に制御します。これにより、GoのビルドシステムがNaClターゲット向けに正確なバイナリを生成できるようになります。
- Goのソースコード全体にわたって、
-
15540045: cmd/6g, cmd/8g, cmd/gc: support for Native Client
:- Goのコンパイラ(
6g
はamd64、8g
は386、gc
は共通フロントエンド)にNaClとamd64p32
アーキテクチャのサポートを追加します。これには、新しいアーキテクチャ向けのコード生成バックエンドの調整、ポインタサイズの変更(amd64p32
の場合)、およびNaClのサンドボックス制約に合わせたコード最適化の調整が含まれます。特に、amd64p32
ではポインタが32ビットになるため、コンパイラはアドレス計算やレジスタの使用方法を適切に調整する必要があります。
- Goのコンパイラ(
-
15570045: os: support for Native Client
:- Goの
os
パッケージは、ファイルシステム、プロセス、環境変数など、OSレベルの機能を提供します。このCLは、os
パッケージがNaCl環境の制約(例えば、fork
/exec
の制限、特定のファイルパスへのアクセス制限など)に対応できるように変更します。
- Goの
-
15680044: crypto/..., hash/crc32, reflect, sync/atomic: support for amd64p32
:amd64p32
アーキテクチャは、64ビットCPU上で32ビットポインタを使用するため、アセンブリコードや低レベルな操作を行うパッケージに影響を与えます。このCLは、crypto
、hash/crc32
、reflect
、sync/atomic
といったパッケージ内のアセンブリコードやポインタ操作を含む部分をamd64p32
に対応させます。特に、reflect
パッケージは型情報の内部表現にポインタサイズが影響するため、重要な変更が必要です。
-
15690044: net: support for Native Client
:- Goの
net
パッケージは、ネットワーク通信機能を提供します。NaCl環境では、ネットワークアクセスはブラウザのセキュリティモデルによって厳しく管理されます。このCLは、net
パッケージがNaClのネットワークAPI(SRPCを介して提供される)を利用して、ソケット操作やDNSルックアップなどを実行できるように変更します。
- Goの
-
15690048: runtime: support for fake time like on Go Playground
:- Go Playgroundのようなサンドボックス環境では、システム時刻を直接参照するのではなく、制御された「偽の時刻」を使用することがあります。NaClもサンドボックス環境であるため、このCLはランタイムに同様の偽の時刻サポートを導入し、NaCl環境での時間関連の操作をより予測可能で安全にします。
-
15690051: build: disable various tests on Native Client
:- NaCl環境の制約や特性により、Goの既存のテストの一部がNaCl上で適切に動作しない場合があります。このCLは、NaClターゲットでビルドする際に、これらの互換性のないテストを無効にするためのビルドタグやロジックを追加します。
これらの変更は、Go言語がNaClという新しい、かつ制約の多い環境で動作するための包括的な取り組みの第一歩であり、コンパイラ、ランタイム、標準ライブラリの各層にわたる深い変更を伴います。
コアとなるコードの変更箇所
このコミットは非常に広範な変更を含んでいますが、特にGoのコンパイラ(cmd/6g
, cmd/8g
, cmd/gc
)とランタイム(src/pkg/runtime
)、そしてシステムコール(src/pkg/syscall
)に関連する変更がコアとなります。
いくつかの代表的な変更箇所を挙げます。
-
src/cmd/6g/galign.c
およびsrc/cmd/8g/galign.c
:amd64p32
アーキテクチャのサポートのために、ポインタの幅 (widthptr
) とレジスタの幅 (widthreg
) を動的に設定するロジックが追加されています。amd64p32
の場合、widthptr
は4バイト(32ビット)に設定され、addptr
,movptr
,leaptr
,stosptr
,cmpptr
といったポインタ操作に関連するアセンブリ命令も32ビット版(AADDL
,AMOVL
,ALEAL
など)に切り替わるようになります。
// src/cmd/6g/galign.c // ... int addptr = AADDQ; int movptr = AMOVQ; int leaptr = ALEAQ; int stosptr = ASTOSQ; int cmpptr = ACMPQ; void betypeinit(void) { widthptr = 8; widthint = 8; widthreg = 8; if(strcmp(getgoarch(), "amd64p32") == 0) { widthptr = 4; widthint = 4; addptr = AADDL; movptr = AMOVL; leaptr = ALEAL; stosptr = ASTOSL; cmpptr = ACMPL; typedefs[0].sameas = TINT32; typedefs[1].sameas = TUINT32; typedefs[2].sameas = TUINT32; } // ... }
-
src/cmd/gc/builtin.c
およびsrc/cmd/gc/runtime.go
:- NaCl環境ではゼロ除算トラップが伝播されないため、Goランタイムが明示的にゼロ除算チェックを行い、
panicdivide
関数を呼び出すように変更されています。
--- a/src/cmd/gc/builtin.c +++ b/src/cmd/gc/builtin.c @@ -5,6 +5,7 @@ char *runtimeimport = "func @\"\\\".new (@\"\\\".typ·2 *byte) (? *any)\\n" "func @\"\\\".panicindex ()\\n" "func @\"\\\".panicslice ()\\n" + "func @\"\\\".panicdivide ()\\n" "func @\"\\\".throwreturn ()\\n" "func @\"\\\".throwinit ()\\n" "func @\"\\\".panicwrap (? string, ? string, ? string)\\n"
- NaCl環境ではゼロ除算トラップが伝播されないため、Goランタイムが明示的にゼロ除算チェックを行い、
-
src/cmd/go/build.go
,src/cmd/go/run.go
,src/cmd/go/test.go
:- Goのビルドツールが
nacl
とamd64p32
を新しいOS/アーキテクチャの組み合わせとして認識するように更新されています。特に、go run
やgo test
コマンドで、クロスコンパイルされたバイナリを実行するための-exec
フラグのサポートが追加されています。これは、NaClバイナリをNaClランタイム(sel_ldr
など)で実行するために必要です。
--- a/src/cmd/go/run.go +++ b/src/cmd/go/run.go @@ -8,9 +8,27 @@ import ( "fmt" "os" "os/exec" + "runtime" "strings" ) +var execCmd []string // -exec flag, for run and test + +func findExecCmd() []string { + if execCmd != nil { + return execCmd + } + execCmd = []string{} // avoid work the second time + if goos == runtime.GOOS && goarch == runtime.GOARCH { + return execCmd + } + path, err := exec.LookPath(fmt.Sprintf("go_%s_%s_exec", goos, goarch)) + if err == nil { + execCmd = []string{path} + } + return execCmd +} + var cmdRun = &Command{ UsageLine: "run [build flags] gofiles... [arguments...]", Short: "compile and run Go program", @@ -28,6 +46,7 @@ func init() { cmdRun.Run = runRun // break init loop addBuildFlags(cmdRun) + cmdRun.Flag.Var((*stringsFlag)(&execCmd), "exec", "") } func printStderr(args ...interface{}) (int, error) { @@ -90,20 +109,20 @@ func runRun(cmd *Command, args []string) { // runProgram is the action for running a binary that has already // been compiled. We ignore exit status. func (b *builder) runProgram(a *action) error { + cmdline := stringList(findExecCmd(), a.deps[0].target, a.args) if buildN || buildX { - b.showcmd("", "%s %s", a.deps[0].target, strings.Join(a.args, " ")) + b.showcmd("", "%s", strings.Join(cmdline, " ")) if buildN { return nil } } - runStdin(a.deps[0].target, a.args) + runStdin(cmdline) return nil } // runStdin is like run, but connects Stdin. -func runStdin(cmdargs ...interface{}) { - cmdline := stringList(cmdargs...) +func runStdin(cmdline []string) { cmd := exec.Command(cmdline[0], cmdline[1:]...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout
- Goのビルドツールが
-
src/pkg/crypto/md5/md5block_amd64p32.s
,src/pkg/crypto/rc4/rc4_amd64p32.s
,src/pkg/crypto/sha1/sha1block_amd64p32.s
:- これらのファイルは、
amd64p32
アーキテクチャ向けに最適化されたアセンブリコードです。MD5, RC4, SHA1といった暗号化アルゴリズムのパフォーマンスクリティカルな部分が、32ビットポインタとNaClの制約(例えば、BP
やR15
レジスタの使用制限、2レジスタアドレッシングモードの回避)を考慮して実装されています。
// src/pkg/crypto/md5/md5block_amd64p32.s (抜粋) // Restrictions to make code safe for Native Client: // replace BP with R11, reloaded before use at return. // replace R15 with R11. // ... TEXT ·block(SB),NOSPLIT,$0-32 MOVL dig+0(FP), R11 MOVL p+4(FP), SI MOVL p_len+8(FP), DX SHRQ $6, DX SHLQ $6, DX // ...
- これらのファイルは、
-
src/pkg/net/fd_poll_nacl.go
:- NaCl環境でのファイルディスクリプタのポーリング(I/Oイベントの待機)を扱うための新しいファイルです。NaClでは通常の
epoll
やkqueue
のようなシステムコールが利用できないため、NaCl固有のI/O待機メカニズム(syscall.StopIO
など)を使用するように抽象化されています。
// src/pkg/net/fd_poll_nacl.go (抜粋) package net import ( "syscall" "time" ) type pollDesc struct { fd *netFD closing bool } // ... func (pd *pollDesc) Evict() bool { pd.closing = true if pd.fd != nil { syscall.StopIO(pd.fd.sysfd) // NaCl固有のI/O停止メカニズム } return false } // ...
- NaCl環境でのファイルディスクリプタのポーリング(I/Oイベントの待機)を扱うための新しいファイルです。NaClでは通常の
-
src/pkg/syscall/fs_nacl.go
,src/pkg/syscall/srpc_nacl.go
,src/pkg/syscall/unzip_nacl.go
:- これらはNaCl固有のシステムコール実装の核心部分です。
fs_nacl.go
: NaClのサンドボックス化されたファイルシステム操作(オープン、リード、ライトなど)をGoのsyscall
インターフェースにマッピングします。srpc_nacl.go
: NaClのSRPCプロトコルを介した通信を実装し、GoプログラムがNaClランタイムのサービスを呼び出せるようにします。unzip_nacl.go
:.nexe
ファットバイナリから適切なアーキテクチャのコードを抽出するロジックが含まれています。
これらのファイルは、Goの標準ライブラリがNaCl環境で動作するために不可欠な、低レベルなインターフェースを提供します。
- これらはNaCl固有のシステムコール実装の核心部分です。
コアとなるコードの解説
src/cmd/6g/galign.c
における amd64p32
のポインタ幅設定
この変更は、Goコンパイラがamd64p32
ターゲット向けにコードを生成する際の、ポインタとレジスタのサイズに関する根本的な調整を示しています。
// src/cmd/6g/galign.c
// ...
int addptr = AADDQ; // デフォルトは64ビット加算命令
int movptr = AMOVQ; // デフォルトは64ビット移動命令
int leaptr = ALEAQ; // デフォルトは64ビットアドレス計算命令
int stosptr = ASTOSQ; // デフォルトは64ビットストア命令
int cmpptr = ACMPQ; // デフォルトは64ビット比較命令
void betypeinit(void) {
widthptr = 8; // デフォルトのポインタ幅は8バイト (64ビット)
widthint = 8; // デフォルトのint幅は8バイト (64ビット)
widthreg = 8; // デフォルトのレジスタ幅は8バイト (64ビット)
if(strcmp(getgoarch(), "amd64p32") == 0) { // もしターゲットアーキテクチャがamd64p32なら
widthptr = 4; // ポインタ幅を4バイト (32ビット) に設定
widthint = 4; // int幅を4バイト (32ビット) に設定
// ポインタ操作に関連するアセンブリ命令を32ビット版に切り替える
addptr = AADDL;
movptr = AMOVL;
leaptr = ALEAL;
stosptr = ASTOSL;
cmpptr = ACMPL;
// typedefs[0].sameas = TINT32; // int型をTINT32にマッピング
// typedefs[1].sameas = TUINT32; // uint型をTUINT32にマッピング
// typedefs[2].sameas = TUINT32; // uintptr型をTUINT32にマッピング
}
// ...
}
widthptr
,widthint
,widthreg
: これらはそれぞれ、ポインタ、int
型、および汎用レジスタの幅(バイト単位)を定義する変数です。通常のamd64
ではこれらは8バイトですが、amd64p32
ではポインタとint
型が4バイトに設定されます。widthreg
も4バイトに設定されることで、レジスタ操作も32ビット幅を前提とするようになります。- 命令の切り替え:
addptr
,movptr
などの変数は、Goコンパイラがアセンブリコードを生成する際に使用する命令の種類を決定します。amd64p32
の場合、これらは64ビット命令(AADDQ
,AMOVQ
など)から32ビット命令(AADDL
,AMOVL
など)に切り替えられます。これにより、生成されるコードが32ビットポインタを正しく扱い、メモリ効率を向上させることができます。 - 型定義の変更:
typedefs
の変更は、Goの組み込み型(int
,uint
,uintptr
)がamd64p32
環境でそれぞれ32ビット整数型として扱われることを示唆しています。
この変更は、Goのコンパイラがamd64p32
という特殊なアーキテクチャの特性(64ビット命令セットだが32ビットポインタ)を理解し、それに応じた効率的なコードを生成するための基盤となります。
src/cmd/go/run.go
における -exec
フラグのサポート
この変更は、Goのビルドツールがクロスコンパイルされたバイナリ(特にNaClのような特殊な環境向け)を実行する際の柔軟性を高めます。
// src/cmd/go/run.go (抜粋)
// ...
var execCmd []string // -exec flag, for run and test
func findExecCmd() []string {
if execCmd != nil {
return execCmd
}
execCmd = []string{} // avoid work the second time
if goos == runtime.GOOS && goarch == runtime.GOARCH {
return execCmd // ホストOS/アーキテクチャと同じなら、特別な実行コマンドは不要
}
// クロスコンパイルの場合、go_<GOOS>_<GOARCH>_exec という形式の実行ヘルパーを探す
path, err := exec.LookPath(fmt.Sprintf("go_%s_%s_exec", goos, goarch))
if err == nil {
execCmd = []string{path} // 見つかればそれを実行コマンドとして設定
}
return execCmd
}
// ...
func (b *builder) runProgram(a *action) error {
// 実行コマンドラインを構築。findExecCmd() が返すヘルパーコマンドがあればそれを使用
cmdline := stringList(findExecCmd(), a.deps[0].target, a.args)
if buildN || buildX {
b.showcmd("", "%s", strings.Join(cmdline, " "))
if buildN {
return nil
}
}
runStdin(cmdline) // 構築されたコマンドラインでプログラムを実行
return nil
}
findExecCmd()
: この関数は、現在のビルドターゲット(goos
,goarch
)がホストのOS/アーキテクチャと異なる場合に、特別な実行ヘルパーコマンドを探します。例えば、GOOS=nacl GOARCH=amd64p32 go run main.go
を実行する場合、findExecCmd()
はgo_nacl_amd64p32_exec
という名前の実行可能ファイルをPATH
から探します。-exec
フラグ:go run
やgo test
コマンドに-exec <command>
フラグを渡すことで、ユーザーは明示的に実行ヘルパーを指定できます。例えば、go run -exec "path/to/sel_ldr -args" main.go
のように使用できます。これにより、Goのビルドツールが生成したNaClバイナリを、NaClランタイム(sel_ldr
)を介して実行することが可能になります。runProgram
: このメソッドは、最終的な実行コマンドラインを構築する際にfindExecCmd()
の結果を組み込みます。これにより、クロスコンパイルされたGoプログラムが、ターゲット環境のランタイムやエミュレータを介して透過的に実行されるようになります。
この機能は、開発者が異なるプラットフォーム向けのGoプログラムを、そのプラットフォームの実行環境をシミュレートしながら開発・テストする上で非常に重要です。
src/pkg/net/fd_poll_nacl.go
におけるポーリングの実装
このファイルは、GoのネットワークパッケージがNaCl環境でI/Oイベントを待機する方法を定義しています。通常のOSではepoll
やkqueue
などの効率的なメカニズムが使われますが、NaClのサンドボックスではそれらが利用できません。
// src/pkg/net/fd_poll_nacl.go (抜粋)
package net
import (
"syscall"
"time"
)
type pollDesc struct {
fd *netFD
closing bool
}
func (pd *pollDesc) Init(fd *netFD) error { pd.fd = fd; return nil }
func (pd *pollDesc) Close() {}
func (pd *pollDesc) Lock() {}
func (pd *pollDesc) Unlock() {}
func (pd *pollDesc) Wakeup() {}
func (pd *pollDesc) Evict() bool {
pd.closing = true
if pd.fd != nil {
syscall.StopIO(pd.fd.sysfd) // NaCl固有のI/O停止メカニズム
}
return false
}
func (pd *pollDesc) Prepare(mode int) error {
if pd.closing {
return errClosing
}
return nil
}
func (pd *pollDesc) PrepareRead() error { return pd.Prepare('r') }
func (pd *pollDesc) PrepareWrite() error { return pd.Prepare('w') }
func (pd *pollDesc) Wait(mode int) error {
if pd.closing {
return errClosing
}
// NaCl環境では、ポーリングは常にタイムアウトとして扱われるか、
// 実際にはsyscall.Read/Writeがブロックするのを待つことになる。
// ここでは、ポーリングが成功するまで待つのではなく、
// タイムアウトを返すことで、呼び出し元がブロックするI/O操作を試みるように促す。
return errTimeout
}
func (pd *pollDesc) WaitRead() error { return pd.Wait('r') }
func (pd *pollDesc) WaitWrite() error { return pd.Wait('w') }
func (pd *pollDesc) WaitCanceled(mode int) {}
func (pd *pollDesc) WaitCanceledRead() {}
func (pd *pollDesc) WaitCanceledWrite() {}
// ... setDeadlineImpl 関数もNaCl固有のsyscall.SetReadDeadline/SetWriteDeadlineを使用
pollDesc
: GoのネットワークI/Oにおけるファイルディスクリプタのポーリング状態を管理する構造体です。Evict()
: このメソッドは、ファイルディスクリプタが閉じられる際に呼び出され、syscall.StopIO
を使ってNaClランタイムにI/O操作の停止を通知します。これは、NaClが提供する特殊なAPIです。Wait()
: 注目すべきはWait()
メソッドの実装です。通常のGoランタイムでは、このメソッドはI/Oイベントが発生するまでブロックしますが、NaCl版では常にerrTimeout
を返します。これは、NaClのI/Oモデルが通常のOSとは異なり、Goのランタイムが直接イベントループを制御するのではなく、syscall.Read
やsyscall.Write
のようなブロッキング操作がNaClランタイムによって適切に処理されることを前提としているためと考えられます。つまり、Goのネットワークコードは、イベント駆動ではなく、ブロッキングI/Oを試み、NaClランタイムがそのブロッキングを適切に管理することを期待しているということです。setDeadlineImpl
: この関数は、読み書きのデッドラインを設定するためにsyscall.SetReadDeadline
やsyscall.SetWriteDeadline
といったNaCl固有のシステムコールを呼び出します。
これらの変更は、GoのネットワークスタックがNaClのサンドボックス環境の制約内で機能するために、低レベルなI/O操作をNaClの提供するAPIに適合させるためのものです。
関連リンク
- Go Native Client Design Overview: https://golang.org/s/go13nacl (このコミットメッセージで参照されているリンク)
- Google Native Client Project: https://developer.chrome.com/native-client (現在は非推奨)
- Go言語公式ドキュメント: https://go.dev/doc/
参考にした情報源リンク
- Go言語のソースコード (特にこのコミットのdiff)
- Google Native Clientの公式ドキュメント (過去の資料を含む)
amd64p32
に関する一般的な情報 (メモリモデル、ポインタサイズなど)- Go言語のビルドシステムと
+build
タグに関するドキュメント - Go言語のランタイムとシステムコールに関する一般的な知識
[インデックス 18641] ファイルの概要
このコミットは、Go言語がGoogle Native Client (NaCl) をサポートするための大規模な変更セットの第一弾です。主に、Go 1.2ベースのNaClブランチからの機械的な変更を取り込んでおり、コンパイラ、ランタイム、システムコール、標準ライブラリなど、Goの様々なコンポーネントにNaCl固有のサポートを追加しています。また、amd64p32
アーキテクチャへの対応も含まれています。
コミット
- コミットハッシュ:
7c8280c9efcd24b882e441d359b6880c1a456ad8
- Author: Dave Cheney dave@cheney.net
- Date: Tue Feb 25 09:47:42 2014 -0500
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7c8280c9efcd24b882e441d359b6880c1a456ad8
元コミット内容
all: merge NaCl branch (part 1)
See golang.org/s/go13nacl for design overview.
This CL is the mostly mechanical changes from rsc's Go 1.2 based NaCl branch, specifically 39cb35750369 to 500771b477cf from https://code.google.com/r/rsc-go13nacl. This CL does not include working NaCl support, there are probably two or three more large merges to come.
CL 15750044 is not included as it involves more invasive changes to the linker which will need to be merged separately.
The exact change lists included are
15050047: syscall: support for Native Client
15360044: syscall: unzip implementation for Native Client
15370044: syscall: Native Client SRPC implementation
15400047: cmd/dist, cmd/go, go/build, test: support for Native Client
15410048: runtime: support for Native Client
15410049: syscall: file descriptor table for Native Client
15410050: syscall: in-memory file system for Native Client
15440048: all: update +build lines for Native Client port
15540045: cmd/6g, cmd/8g, cmd/gc: support for Native Client
15570045: os: support for Native Client
15680044: crypto/..., hash/crc32, reflect, sync/atomic: support for amd64p32
15690044: net: support for Native Client
15690048: runtime: support for fake time like on Go Playground
15690051: build: disable various tests on Native Client
LGTM=rsc
R=rsc
CC=golang-codereviews
https://golang.org/cl/68150047
変更の背景
このコミットの主な背景は、Go言語をGoogle Native Client (NaCl) プラットフォームで動作させるためのサポートを追加することです。NaClは、ウェブブラウザ内でネイティブコードを安全に実行するためのサンドボックス技術であり、Goプログラムをウェブアプリケーションの一部として実行できるようにすることを目指していました。
Go言語は元々、様々なOSやアーキテクチャをサポートするように設計されていますが、NaClのような特殊なサンドボックス環境への対応は、通常のOSとは異なるシステムコール、メモリ管理、ファイルシステムなどの制約を考慮する必要があります。このコミットは、そのための基盤となる変更をGoのツールチェイン、ランタイム、標準ライブラリに導入する「パート1」として位置づけられています。
特に、amd64p32
アーキテクチャのサポートも含まれており、これは64ビットCPU上で32ビットポインタを使用する特殊なモードで、NaCl環境での効率的な実行を目的としています。
前提知識の解説
Google Native Client (NaCl) と Portable Native Client (PNaCl)
Google Native Client (NaCl) は、ウェブブラウザ内でネイティブコード(C/C++など)を安全に実行するためのサンドボックス技術です。これにより、ウェブアプリケーションは、JavaScriptのパフォーマンス限界を超えて、CPUに近い速度で計算集約的なタスクを実行できるようになります。NaClは、セキュリティモデルとして、コードの検証、メモリ分離、システムコール制限などを採用しています。
Portable Native Client (PNaCl) は、NaClの進化版であり、特定のCPUアーキテクチャに依存しない中間表現(LLVM IRベース)を使用します。これにより、開発者は一度コンパイルすれば、PNaClランタイムがサポートする任意のアーキテクチャ(x86-32, x86-64, ARMなど)で実行可能なバイナリを生成できます。ブラウザは、PNaClモジュールをダウンロードし、実行時にターゲットアーキテクチャのネイティブコードにJITコンパイルします。
Go言語がNaClをサポートするということは、Goで書かれたアプリケーションがウェブブラウザのサンドボックス内で、ネイティブに近いパフォーマンスで動作する可能性を開くものでした。
amd64p32
アーキテクチャ
amd64p32
は、64ビットのAMD64 (x86-64) 命令セットを使用しながら、ポインタサイズを32ビットに制限する特殊な実行モードです。これは、特にメモリ使用量を削減し、キャッシュ効率を向上させるために設計されました。
通常の64ビット環境ではポインタは8バイト(64ビット)ですが、amd64p32
では4バイト(32ビット)になります。これにより、データ構造内のポインタが占めるメモリが半分になり、全体的なメモリフットプリントが小さくなります。これは、特に大量のポインタを使用するアプリケーションや、メモリが限られた環境(例えば、ウェブブラウザのサンドボックス内)で有利に働きます。
NaCl環境では、メモリの割り当てやアクセスに厳しい制限があるため、amd64p32
のようなメモリ効率の良いアーキテクチャは非常に重要です。Go言語のランタイムやコンパイラがこのモードをサポートすることは、NaCl上でのGoプログラムの実行効率を向上させる上で不可欠な要素となります。
Go言語のビルドタグ (+build
)
Go言語のソースファイルには、+build
ディレクティブを使用してビルドタグを記述することができます。これは、特定のOS、アーキテクチャ、またはカスタムタグに基づいて、そのファイルをビルドに含めるかどうかを制御するために使用されます。例えば、// +build linux,amd64
と書かれたファイルは、LinuxかつAMD64アーキテクチャの場合にのみビルドに含まれます。
このコミットでは、+build nacl
や+build amd64p32
といった新しいタグが導入され、NaCl固有のコードやamd64p32
固有のコードをGoのビルドシステムが適切に認識し、コンパイルできるように変更されています。
技術的詳細
このコミットは、Go言語をNaCl環境に移植するための多岐にわたる変更を含んでいます。以下に、コミットメッセージに記載されている主要な変更リスト(CLs)に基づいて技術的な詳細を解説します。
-
15050047: syscall: support for Native Client
:- Goのシステムコール層にNaCl固有のシステムコールインターフェースを追加します。NaClはホストOSのシステムコールを直接呼び出すのではなく、独自のIPC (Inter-Process Communication) メカニズムであるSRPC (Simple RPC) を介して、ブラウザのプロセスやNaClランタイムと通信します。このCLは、Goの
syscall
パッケージがNaClのSRPCを介して基本的なOSサービス(ファイル操作、ネットワークなど)にアクセスできるようにするための基盤を構築します。
- Goのシステムコール層にNaCl固有のシステムコールインターフェースを追加します。NaClはホストOSのシステムコールを直接呼び出すのではなく、独自のIPC (Inter-Process Communication) メカニズムであるSRPC (Simple RPC) を介して、ブラウザのプロセスやNaClランタイムと通信します。このCLは、Goの
-
15360044: syscall: unzip implementation for Native Client
:- NaCl環境では、実行可能ファイルが
.nexe
(Native Client Executable)形式で提供されます。これは通常、複数のアーキテクチャ(x86-32, x86-64, ARM)のコードを含む「ファットバイナリ」として配布されます。このCLは、Goのランタイムが.nexe
ファイル内の適切なアーキテクチャのコードを「解凍」または選択して実行するためのメカニズムをsyscall
パッケージ内に実装します。
- NaCl環境では、実行可能ファイルが
-
15370044: syscall: Native Client SRPC implementation
:- NaClのSRPC (Simple RPC) は、NaClモジュールがブラウザやホストシステムと通信するための主要な手段です。このCLは、Goの
syscall
パッケージ内にSRPCクライアントの実装を追加し、GoプログラムがNaClの提供するサービス(例えば、ファイルシステムアクセス、ネットワークソケット操作など)を呼び出せるようにします。これは、Goの標準ライブラリがNaCl上で機能するために不可欠です。
- NaClのSRPC (Simple RPC) は、NaClモジュールがブラウザやホストシステムと通信するための主要な手段です。このCLは、Goの
-
15400047: cmd/dist, cmd/go, go/build, test: support for Native Client
:- Goのビルドツールチェイン(
cmd/dist
,cmd/go
)とビルドシステム(go/build
パッケージ)にNaClのサポートを追加します。これには、新しいGOOS=nacl
とGOARCH=amd64p32
(または他のNaClサポートアーキテクチャ)の組み合わせを認識し、それに応じたクロスコンパイルを可能にする変更が含まれます。また、テストフレームワークもNaCl環境で動作するように調整されます。
- Goのビルドツールチェイン(
-
15410048: runtime: support for Native Client
:- Goランタイムは、ガベージコレクション、スケジューラ、メモリ管理など、Goプログラムの実行を支える中核部分です。このCLは、NaClのサンドボックス環境の制約(例えば、直接的なメモリマップ操作の制限、特定のシステムコールへのアクセス不可など)に対応するために、ランタイムの低レベルな部分を変更します。これには、NaCl固有のメモリ割り当て、スレッド管理、シグナルハンドリングの調整が含まれます。
-
15410049: syscall: file descriptor table for Native Client
:- NaClは、ホストOSのファイルディスクリプタを直接使用するのではなく、独自のファイルディスクリプタ管理システムを持っています。このCLは、Goの
syscall
パッケージ内にNaClのファイルディスクリプタテーブルを管理するロジックを導入し、GoプログラムがNaClのファイルI/O操作を透過的に実行できるようにします。
- NaClは、ホストOSのファイルディスクリプタを直接使用するのではなく、独自のファイルディスクリプタ管理システムを持っています。このCLは、Goの
-
15410050: syscall: in-memory file system for Native Client
:- NaCl環境では、通常のファイルシステムアクセスが制限されるか、または存在しない場合があります。このCLは、Goの
syscall
パッケージ内にインメモリファイルシステム(またはそれに類する抽象化)のサポートを追加し、Goプログラムがファイル操作を必要とする場合に、NaClのサンドボックス内で動作できるようにします。これは、Goの標準ライブラリがファイルI/Oに依存しているため重要です。
- NaCl環境では、通常のファイルシステムアクセスが制限されるか、または存在しない場合があります。このCLは、Goの
-
15440048: all: update +build lines for Native Client port
:- Goのソースコード全体にわたって、
+build nacl
タグを追加または更新し、NaCl固有のコードパスや、NaCl環境で無効化すべきコードパスを適切に制御します。これにより、GoのビルドシステムがNaClターゲット向けに正確なバイナリを生成できるようになります。
- Goのソースコード全体にわたって、
-
15540045: cmd/6g, cmd/8g, cmd/gc: support for Native Client
:- Goのコンパイラ(
6g
はamd64、8g
は386、gc
は共通フロントエンド)にNaClとamd64p32
アーキテクチャのサポートを追加します。これには、新しいアーキテクチャ向けのコード生成バックエンドの調整、ポインタサイズの変更(amd64p32
の場合)、およびNaClのサンドボックス制約に合わせたコード最適化の調整が含まれます。特に、amd64p32
ではポインタが32ビットになるため、コンパイラはアドレス計算やレジスタの使用方法を適切に調整する必要があります。
- Goのコンパイラ(
-
15570045: os: support for Native Client
:- Goの
os
パッケージは、ファイルシステム、プロセス、環境変数など、OSレベルの機能を提供します。このCLは、os
パッケージがNaCl環境の制約(例えば、fork
/exec
の制限、特定のファイルパスへのアクセス制限など)に対応できるように変更します。
- Goの
-
15680044: crypto/..., hash/crc32, reflect, sync/atomic: support for amd64p32
:amd64p32
アーキテクチャは、64ビットCPU上で32ビットポインタを使用するため、アセンブリコードや低レベルな操作を行うパッケージに影響を与えます。このCLは、crypto
、hash/crc32
、reflect
、sync/atomic
といったパッケージ内のアセンブリコードやポインタ操作を含む部分をamd64p32
に対応させます。特に、reflect
パッケージは型情報の内部表現にポインタサイズが影響するため、重要な変更が必要です。
-
15690044: net: support for Native Client
:- Goの
net
パッケージは、ネットワーク通信機能を提供します。NaCl環境では、ネットワークアクセスはブラウザのセキュリティモデルによって厳しく管理されます。このCLは、net
パッケージがNaClのネットワークAPI(SRPCを介して提供される)を利用して、ソケット操作やDNSルックアップなどを実行できるように変更します。
- Goの
-
15690048: runtime: support for fake time like on Go Playground
:- Go Playgroundのようなサンドボックス環境では、システム時刻を直接参照するのではなく、制御された「偽の時刻」を使用することがあります。NaClもサンドボックス環境であるため、このCLはランタイムに同様の偽の時刻サポートを導入し、NaCl環境での時間関連の操作をより予測可能で安全にします。
-
15690051: build: disable various tests on Native Client
:- NaCl環境の制約や特性により、Goの既存のテストの一部がNaCl上で適切に動作しない場合があります。このCLは、NaClターゲットでビルドする際に、これらの互換性のないテストを無効にするためのビルドタグやロジックを追加します。
これらの変更は、Go言語がNaClという新しい、かつ制約の多い環境で動作するための包括的な取り組みの第一歩であり、コンパイラ、ランタイム、標準ライブラリの各層にわたる深い変更を伴います。
コアとなるコードの変更箇所
このコミットは非常に広範な変更を含んでいますが、特にGoのコンパイラ(cmd/6g
, cmd/8g
, cmd/gc
)とランタイム(src/pkg/runtime
)、そしてシステムコール(src/pkg/syscall
)に関連する変更がコアとなります。
いくつかの代表的な変更箇所を挙げます。
-
src/cmd/6g/galign.c
およびsrc/cmd/8g/galign.c
:amd64p32
アーキテクチャのサポートのために、ポインタの幅 (widthptr
) とレジスタの幅 (widthreg
) を動的に設定するロジックが追加されています。amd64p32
の場合、widthptr
は4バイト(32ビット)に設定され、addptr
,movptr
,leaptr
,stosptr
,cmpptr
といったポインタ操作に関連するアセンブリ命令も32ビット版(AADDL
,AMOVL
,ALEAL
など)に切り替わるようになります。
// src/cmd/6g/galign.c // ... int addptr = AADDQ; int movptr = AMOVQ; int leaptr = ALEAQ; int stosptr = ASTOSQ; int cmpptr = ACMPQ; void betypeinit(void) { widthptr = 8; widthint = 8; widthreg = 8; if(strcmp(getgoarch(), "amd64p32") == 0) { // もしターゲットアーキテクチャがamd64p32なら widthptr = 4; // ポインタ幅を4バイト (32ビット) に設定 widthint = 4; // int幅を4バイト (32ビット) に設定 addptr = AADDL; movptr = AMOVL; leaptr = ALEAL; stosptr = ASTOSL; cmpptr = ACMPL; typedefs[0].sameas = TINT32; typedefs[1].sameas = TUINT32; typedefs[2].sameas = TUINT32; } // ... }
-
src/cmd/gc/builtin.c
およびsrc/cmd/gc/runtime.go
:- NaCl環境ではゼロ除算トラップが伝播されないため、Goランタイムが明示的にゼロ除算チェックを行い、
panicdivide
関数を呼び出すように変更されています。
--- a/src/cmd/gc/builtin.c +++ b/src/cmd/gc/builtin.c @@ -5,6 +5,7 @@ char *runtimeimport = "func @\"\\\".new (@\"\\\".typ·2 *byte) (? *any)\\n" "func @\"\\\".panicindex ()\\n" "func @\"\\\".panicslice ()\\n" + "func @\"\\\".panicdivide ()\\n" "func @\"\\\".throwreturn ()\\n" "func @\"\\\".throwinit ()\\n" "func @\"\\\".panicwrap (? string, ? string, ? string)\\n"
- NaCl環境ではゼロ除算トラップが伝播されないため、Goランタイムが明示的にゼロ除算チェックを行い、
-
src/cmd/go/build.go
,src/cmd/go/run.go
,src/cmd/go/test.go
:- Goのビルドツールが
nacl
とamd64p32
を新しいOS/アーキテクチャの組み合わせとして認識するように更新されています。特に、go run
やgo test
コマンドで、クロスコンパイルされたバイナリを実行するための-exec
フラグのサポートが追加されています。これは、NaClバイナリをNaClランタイム(sel_ldr
など)で実行するために必要です。
--- a/src/cmd/go/run.go +++ b/src/cmd/go/run.go @@ -8,9 +8,27 @@ import ( "fmt" "os" "os/exec" + "runtime" "strings" ) +var execCmd []string // -exec flag, for run and test + +func findExecCmd() []string { + if execCmd != nil { + return execCmd + } + execCmd = []string{} // avoid work the second time + if goos == runtime.GOOS && goarch == runtime.GOARCH { + return execCmd + } + path, err := exec.LookPath(fmt.Sprintf("go_%s_%s_exec", goos, goarch)) + if err == nil { + execCmd = []string{path} + } + return execCmd +} + var cmdRun = &Command{ UsageLine: "run [build flags] gofiles... [arguments...]", Short: "compile and run Go program", @@ -28,6 +46,7 @@ func init() { cmdRun.Run = runRun // break init loop addBuildFlags(cmdRun) + cmdRun.Flag.Var((*stringsFlag)(&execCmd), "exec", "") } func printStderr(args ...interface{}) (int, error) { @@ -90,20 +109,20 @@ func runRun(cmd *Command, args []string) { // runProgram is the action for running a binary that has already // been compiled. We ignore exit status. func (b *builder) runProgram(a *action) error { + cmdline := stringList(findExecCmd(), a.deps[0].target, a.args) if buildN || buildX { - b.showcmd("", "%s %s", a.deps[0].target, strings.Join(a.args, " ")) + b.showcmd("", "%s", strings.Join(cmdline, " ")) if buildN { return nil } } - runStdin(a.deps[0].target, a.args) + runStdin(cmdline) return nil } // runStdin is like run, but connects Stdin. -func runStdin(cmdargs ...interface{}) { - cmdline := stringList(cmdargs...) +func runStdin(cmdline []string) { cmd := exec.Command(cmdline[0], cmdline[1:]...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout
- Goのビルドツールが
-
src/pkg/crypto/md5/md5block_amd64p32.s
,src/pkg/crypto/rc4/rc4_amd64p32.s
,src/pkg/crypto/sha1/sha1block_amd64p32.s
:- これらのファイルは、
amd64p32
アーキテクチャ向けに最適化されたアセンブリコードです。MD5, RC4, SHA1といった暗号化アルゴリズムのパフォーマンスクリティカルな部分が、32ビットポインタとNaClの制約(例えば、BP
やR15
レジスタの使用制限、2レジスタアドレッシングモードの回避)を考慮して実装されています。
// src/pkg/crypto/md5/md5block_amd64p32.s (抜粋) // Restrictions to make code safe for Native Client: // replace BP with R11, reloaded before use at return. // replace R15 with R11. // ... TEXT ·block(SB),NOSPLIT,$0-32 MOVL dig+0(FP), R11 MOVL p+4(FP), SI MOVL p_len+8(FP), DX SHRQ $6, DX SHLQ $6, DX // ...
- これらのファイルは、
-
src/pkg/net/fd_poll_nacl.go
:- NaCl環境でのファイルディスクリプタのポーリング(I/Oイベントの待機)を扱うための新しいファイルです。NaClでは通常の
epoll
やkqueue
のようなシステムコールが利用できないため、NaCl固有のI/O待機メカニズム(syscall.StopIO
など)を使用するように抽象化されています。
// src/pkg/net/fd_poll_nacl.go (抜粋) package net import ( "syscall" "time" ) type pollDesc struct { fd *netFD closing bool } func (pd *pollDesc) Init(fd *netFD) error { pd.fd = fd; return nil } func (pd *pollDesc) Close() {} func (pd *pollDesc) Lock() {} func (pd *pollDesc) Unlock() {} func (pd *pollDesc) Wakeup() {} func (pd *pollDesc) Evict() bool { pd.closing = true if pd.fd != nil { syscall.StopIO(pd.fd.sysfd) // NaCl固有のI/O停止メカニズム } return false } func (pd *pollDesc) Prepare(mode int) error { if pd.closing { return errClosing } return nil } func (pd *pollDesc) PrepareRead() error { return pd.Prepare('r') } func (pd *pollDesc) PrepareWrite() error { return pd.Prepare('w') } func (pd *pollDesc) Wait(mode int) error { if pd.closing { return errClosing } // NaCl環境では、ポーリングは常にタイムアウトとして扱われるか、 // 実際にはsyscall.Read/Writeがブロックするのを待つことになる。 // ここでは、ポーリングが成功するまで待つのではなく、 // タイムアウトを返すことで、呼び出し元がブロックするI/O操作を試みるように促す。 return errTimeout } func (pd *pollDesc) WaitRead() error { return pd.Wait('r') } func (pd *pollDesc) WaitWrite() error { return pd.Wait('w') } func (pd *pollDesc) WaitCanceled(mode int) {} func (pd *pollDesc) WaitCanceledRead() {} func (pd *pollDesc) WaitCanceledWrite() {} // ... setDeadlineImpl 関数もNaCl固有のsyscall.SetReadDeadline/SetWriteDeadlineを使用
- NaCl環境でのファイルディスクリプタのポーリング(I/Oイベントの待機)を扱うための新しいファイルです。NaClでは通常の
-
src/pkg/syscall/fs_nacl.go
,src/pkg/syscall/srpc_nacl.go
,src/pkg/syscall/unzip_nacl.go
:- これらはNaCl固有のシステムコール実装の核心部分です。
fs_nacl.go
: NaClのサンドボックス化されたファイルシステム操作(オープン、リード、ライトなど)をGoのsyscall
インターフェースにマッピングします。srpc_nacl.go
: NaClのSRPCプロトコルを介した通信を実装し、GoプログラムがNaClランタイムのサービスを呼び出せるようにします。unzip_nacl.go
:.nexe
ファットバイナリから適切なアーキテクチャのコードを抽出するロジックが含まれています。
これらのファイルは、Goの標準ライブラリがNaCl環境で動作するために不可欠な、低レベルなインターフェースを提供します。
- これらはNaCl固有のシステムコール実装の核心部分です。
コアとなるコードの解説
src/cmd/6g/galign.c
における amd64p32
のポインタ幅設定
この変更は、Goコンパイラがamd64p32
ターゲット向けにコードを生成する際の、ポインタとレジスタのサイズに関する根本的な調整を示しています。
// src/cmd/6g/galign.c
// ...
int addptr = AADDQ; // デフォルトは64ビット加算命令
int movptr = AMOVQ; // デフォルトは64ビット移動命令
int leaptr = ALEAQ; // デフォルトは64ビットアドレス計算命令
int stosptr = ASTOSQ; // デフォルトは64ビットストア命令
int cmpptr = ACMPQ; // デフォルトは64ビット比較命令
void betypeinit(void) {
widthptr = 8; // デフォルトのポインタ幅は8バイト (64ビット)
widthint = 8; // デフォルトのint幅は8バイト (64ビット)
widthreg = 8; // デフォルトのレジスタ幅は8バイト (64ビット)
if(strcmp(getgoarch(), "amd64p32") == 0) { // もしターゲットアーキテクチャがamd64p32なら
widthptr = 4; // ポインタ幅を4バイト (32ビット) に設定
widthint = 4; // int幅を4バイト (32ビット) に設定
// ポインタ操作に関連するアセンブリ命令を32ビット版に切り替える
addptr = AADDL;
movptr = AMOVL;
leaptr = ALEAL;
stosptr = ASTOSL;
cmpptr = ACMPL;
// typedefs[0].sameas = TINT32; // int型をTINT32にマッピング
// typedefs[1].sameas = TUINT32; // uint型をTUINT32にマッピング
// typedefs[2].sameas = TUINT32; // uintptr型をTUINT32にマッピング
}
// ...
}
widthptr
,widthint
,widthreg
: これらはそれぞれ、ポインタ、int
型、および汎用レジスタの幅(バイト単位)を定義する変数です。通常のamd64
ではこれらは8バイトですが、amd64p32
ではポインタとint
型が4バイトに設定されます。widthreg
も4バイトに設定されることで、レジスタ操作も32ビット幅を前提とするようになります。- 命令の切り替え:
addptr
,movptr
などの変数は、Goコンパイラがアセンブリコードを生成する際に使用する命令の種類を決定します。amd64p32
の場合、これらは64ビット命令(AADDQ
,AMOVQ
など)から32ビット命令(AADDL
,AMOVL
など)に切り替えられます。これにより、生成されるコードが32ビットポインタを正しく扱い、メモリ効率を向上させることができます。 - 型定義の変更:
typedefs
の変更は、Goの組み込み型(int
,uint
,uintptr
)がamd64p32
環境でそれぞれ32ビット整数型として扱われることを示唆しています。
この変更は、Goのコンパイラがamd64p32
という特殊なアーキテクチャの特性(64ビット命令セットだが32ビットポインタ)を理解し、それに応じた効率的なコードを生成するための基盤となります。
src/cmd/go/run.go
における -exec
フラグのサポート
この変更は、Goのビルドツールがクロスコンパイルされたバイナリ(特にNaClのような特殊な環境向け)を実行する際の柔軟性を高めます。
// src/cmd/go/run.go (抜粋)
// ...
var execCmd []string // -exec flag, for run and test
func findExecCmd() []string {
if execCmd != nil {
return execCmd
}
execCmd = []string{} // avoid work the second time
if goos == runtime.GOOS && goarch == runtime.GOARCH {
return execCmd // ホストOS/アーキテクチャと同じなら、特別な実行コマンドは不要
}
// クロスコンパイルの場合、go_<GOOS>_<GOARCH>_exec という形式の実行ヘルパーを探す
path, err := exec.LookPath(fmt.Sprintf("go_%s_%s_exec", goos, goarch))
if err == nil {
execCmd = []string{path} // 見つかればそれを実行コマンドとして設定
}
return execCmd
}
// ...
func (b *builder) runProgram(a *action) error {
// 実行コマンドラインを構築。findExecCmd() が返すヘルパーコマンドがあればそれを使用
cmdline := stringList(findExecCmd(), a.deps[0].target, a.args)
if buildN || buildX {
b.showcmd("", "%s", strings.Join(cmdline, " "))
if buildN {
return nil
}
}
runStdin(cmdline) // 構築されたコマンドラインでプログラムを実行
return nil
}
findExecCmd()
: この関数は、現在のビルドターゲット(goos
,goarch
)がホストのOS/アーキテクチャと異なる場合に、特別な実行ヘルパーコマンドを探します。例えば、GOOS=nacl GOARCH=amd64p32 go run main.go
を実行する場合、findExecCmd()
はgo_nacl_amd64p32_exec
という名前の実行可能ファイルをPATH
から探します。-exec
フラグ:go run
やgo test
コマンドに-exec <command>
フラグを渡すことで、ユーザーは明示的に実行ヘルパーを指定できます。例えば、go run -exec "path/to/sel_ldr -args" main.go
のように使用できます。これにより、Goのビルドツールが生成したNaClバイナリを、NaClランタイム(sel_ldr
)を介して実行することが可能になります。runProgram
: このメソッドは、最終的な実行コマンドラインを構築する際にfindExecCmd()
の結果を組み込みます。これにより、クロスコンパイルされたGoプログラムが、ターゲット環境のランタイムやエミュレータを介して透過的に実行されるようになります。
この機能は、開発者が異なるプラットフォーム向けのGoプログラムを、そのプラットフォームの実行環境をシミュレートしながら開発・テストする上で非常に重要です。
src/pkg/net/fd_poll_nacl.go
におけるポーリングの実装
このファイルは、GoのネットワークパッケージがNaCl環境でI/Oイベントを待機する方法を定義しています。通常のOSではepoll
やkqueue
などの効率的なメカニズムが使われますが、NaClのサンドボックスではそれらが利用できません。
// src/pkg/net/fd_poll_nacl.go (抜粋)
package net
import (
"syscall"
"time"
)
type pollDesc struct {
fd *netFD
closing bool
}
func (pd *pollDesc) Init(fd *netFD) error { pd.fd = fd; return nil }
func (pd *pollDesc) Close() {}
func (pd *pollDesc) Lock() {}
func (pd *pollDesc) Unlock() {}
func (pd *pollDesc) Wakeup() {}
func (pd *pollDesc) Evict() bool {
pd.closing = true
if pd.fd != nil {
syscall.StopIO(pd.fd.sysfd) // NaCl固有のI/O停止メカニズム
}
return false
}
func (pd *pollDesc) Prepare(mode int) error {
if pd.closing {
return errClosing
}
return nil
}
func (pd *pollDesc) PrepareRead() error { return pd.Prepare('r') }
func (pd *pollDesc) PrepareWrite() error { return pd.Prepare('w') }
func (pd *pollDesc) Wait(mode int) error {
if pd.closing {
return errClosing
}
// NaCl環境では、ポーリングは常にタイムアウトとして扱われるか、
// 実際にはsyscall.Read/Writeがブロックするのを待つことになる。
// ここでは、ポーリングが成功するまで待つのではなく、
// タイムアウトを返すことで、呼び出し元がブロックするI/O操作を試みるように促す。
return errTimeout
}
func (pd *pollDesc) WaitRead() error { return pd.Wait('r') }
func (pd *pollDesc) WaitWrite() error { return pd.Wait('w') }
func (pd *pollDesc) WaitCanceled(mode int) {}
func (pd *pollDesc) WaitCanceledRead() {}
func (pd *pollDesc) WaitCanceledWrite() {}
// ... setDeadlineImpl 関数もNaCl固有のsyscall.SetReadDeadline/SetWriteDeadlineを使用
pollDesc
: GoのネットワークI/Oにおけるファイルディスクリプタのポーリング状態を管理する構造体です。Evict()
: このメソッドは、ファイルディスクリプタが閉じられる際に呼び出され、syscall.StopIO
を使ってNaClランタイムにI/O操作の停止を通知します。これは、NaClが提供する特殊なAPIです。Wait()
: 注目すべきはWait()
メソッドの実装です。通常のGoランタイムでは、このメソッドはI/Oイベントが発生するまでブロックしますが、NaCl版では常にerrTimeout
を返します。これは、NaClのI/Oモデルが通常のOSとは異なり、Goのランタイムが直接イベントループを制御するのではなく、syscall.Read
やsyscall.Write
のようなブロッキング操作がNaClランタイムによって適切に処理されることを前提としているためと考えられます。つまり、Goのネットワークコードは、イベント駆動ではなく、ブロッキングI/Oを試み、NaClランタイムがそのブロッキングを適切に管理することを期待しているということです。setDeadlineImpl
: この関数は、読み書きのデッドラインを設定するためにsyscall.SetReadDeadline
やsyscall.SetWriteDeadline
といったNaCl固有のシステムコールを呼び出します。
これらの変更は、GoのネットワークスタックがNaClのサンドボックス環境の制約内で機能するために、低レベルなI/O操作をNaClの提供するAPIに適合させるためのものです。
関連リンク
- Go Native Client Design Overview: https://golang.org/s/go13nacl (このコミットメッセージで参照されているリンク)
- Google Native Client Project: https://developer.chrome.com/native-client (現在は非推奨)
- Go言語公式ドキュメント: https://go.dev/doc/
参考にした情報源リンク
- Go言語のソースコード (特にこのコミットのdiff)
- Google Native Clientの公式ドキュメント (過去の資料を含む)
amd64p32
に関する一般的な情報 (メモリモデル、ポインタサイズなど)- Go言語のビルドシステムと
+build
タグに関するドキュメント - Go言語のランタイムとシステムコールに関する一般的な知識