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

[インデックス 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)に基づいて技術的な詳細を解説します。

  1. 15050047: syscall: support for Native Client:

    • Goのシステムコール層にNaCl固有のシステムコールインターフェースを追加します。NaClはホストOSのシステムコールを直接呼び出すのではなく、独自のIPC (Inter-Process Communication) メカニズムであるSRPC (Simple RPC) を介して、ブラウザのプロセスやNaClランタイムと通信します。このCLは、GoのsyscallパッケージがNaClのSRPCを介して基本的なOSサービス(ファイル操作、ネットワークなど)にアクセスできるようにするための基盤を構築します。
  2. 15360044: syscall: unzip implementation for Native Client:

    • NaCl環境では、実行可能ファイルが.nexe(Native Client Executable)形式で提供されます。これは通常、複数のアーキテクチャ(x86-32, x86-64, ARM)のコードを含む「ファットバイナリ」として配布されます。このCLは、Goのランタイムが.nexeファイル内の適切なアーキテクチャのコードを「解凍」または選択して実行するためのメカニズムをsyscallパッケージ内に実装します。
  3. 15370044: syscall: Native Client SRPC implementation:

    • NaClのSRPC (Simple RPC) は、NaClモジュールがブラウザやホストシステムと通信するための主要な手段です。このCLは、Goのsyscallパッケージ内にSRPCクライアントの実装を追加し、GoプログラムがNaClの提供するサービス(例えば、ファイルシステムアクセス、ネットワークソケット操作など)を呼び出せるようにします。これは、Goの標準ライブラリがNaCl上で機能するために不可欠です。
  4. 15400047: cmd/dist, cmd/go, go/build, test: support for Native Client:

    • Goのビルドツールチェイン(cmd/dist, cmd/go)とビルドシステム(go/buildパッケージ)にNaClのサポートを追加します。これには、新しいGOOS=naclGOARCH=amd64p32(または他のNaClサポートアーキテクチャ)の組み合わせを認識し、それに応じたクロスコンパイルを可能にする変更が含まれます。また、テストフレームワークもNaCl環境で動作するように調整されます。
  5. 15410048: runtime: support for Native Client:

    • Goランタイムは、ガベージコレクション、スケジューラ、メモリ管理など、Goプログラムの実行を支える中核部分です。このCLは、NaClのサンドボックス環境の制約(例えば、直接的なメモリマップ操作の制限、特定のシステムコールへのアクセス不可など)に対応するために、ランタイムの低レベルな部分を変更します。これには、NaCl固有のメモリ割り当て、スレッド管理、シグナルハンドリングの調整が含まれます。
  6. 15410049: syscall: file descriptor table for Native Client:

    • NaClは、ホストOSのファイルディスクリプタを直接使用するのではなく、独自のファイルディスクリプタ管理システムを持っています。このCLは、Goのsyscallパッケージ内にNaClのファイルディスクリプタテーブルを管理するロジックを導入し、GoプログラムがNaClのファイルI/O操作を透過的に実行できるようにします。
  7. 15410050: syscall: in-memory file system for Native Client:

    • NaCl環境では、通常のファイルシステムアクセスが制限されるか、または存在しない場合があります。このCLは、Goのsyscallパッケージ内にインメモリファイルシステム(またはそれに類する抽象化)のサポートを追加し、Goプログラムがファイル操作を必要とする場合に、NaClのサンドボックス内で動作できるようにします。これは、Goの標準ライブラリがファイルI/Oに依存しているため重要です。
  8. 15440048: all: update +build lines for Native Client port:

    • Goのソースコード全体にわたって、+build naclタグを追加または更新し、NaCl固有のコードパスや、NaCl環境で無効化すべきコードパスを適切に制御します。これにより、GoのビルドシステムがNaClターゲット向けに正確なバイナリを生成できるようになります。
  9. 15540045: cmd/6g, cmd/8g, cmd/gc: support for Native Client:

    • Goのコンパイラ(6gはamd64、8gは386、gcは共通フロントエンド)にNaClとamd64p32アーキテクチャのサポートを追加します。これには、新しいアーキテクチャ向けのコード生成バックエンドの調整、ポインタサイズの変更(amd64p32の場合)、およびNaClのサンドボックス制約に合わせたコード最適化の調整が含まれます。特に、amd64p32ではポインタが32ビットになるため、コンパイラはアドレス計算やレジスタの使用方法を適切に調整する必要があります。
  10. 15570045: os: support for Native Client:

    • Goのosパッケージは、ファイルシステム、プロセス、環境変数など、OSレベルの機能を提供します。このCLは、osパッケージがNaCl環境の制約(例えば、fork/execの制限、特定のファイルパスへのアクセス制限など)に対応できるように変更します。
  11. 15680044: crypto/..., hash/crc32, reflect, sync/atomic: support for amd64p32:

    • amd64p32アーキテクチャは、64ビットCPU上で32ビットポインタを使用するため、アセンブリコードや低レベルな操作を行うパッケージに影響を与えます。このCLは、cryptohash/crc32reflectsync/atomicといったパッケージ内のアセンブリコードやポインタ操作を含む部分をamd64p32に対応させます。特に、reflectパッケージは型情報の内部表現にポインタサイズが影響するため、重要な変更が必要です。
  12. 15690044: net: support for Native Client:

    • Goのnetパッケージは、ネットワーク通信機能を提供します。NaCl環境では、ネットワークアクセスはブラウザのセキュリティモデルによって厳しく管理されます。このCLは、netパッケージがNaClのネットワークAPI(SRPCを介して提供される)を利用して、ソケット操作やDNSルックアップなどを実行できるように変更します。
  13. 15690048: runtime: support for fake time like on Go Playground:

    • Go Playgroundのようなサンドボックス環境では、システム時刻を直接参照するのではなく、制御された「偽の時刻」を使用することがあります。NaClもサンドボックス環境であるため、このCLはランタイムに同様の偽の時刻サポートを導入し、NaCl環境での時間関連の操作をより予測可能で安全にします。
  14. 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)に関連する変更がコアとなります。

いくつかの代表的な変更箇所を挙げます。

  1. 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;
        }
        // ...
    }
    
  2. 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"
    
  3. src/cmd/go/build.go, src/cmd/go/run.go, src/cmd/go/test.go:

    • Goのビルドツールがnaclamd64p32を新しいOS/アーキテクチャの組み合わせとして認識するように更新されています。特に、go rungo 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
    
  4. 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の制約(例えば、BPR15レジスタの使用制限、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
    // ...
    
  5. src/pkg/net/fd_poll_nacl.go:

    • NaCl環境でのファイルディスクリプタのポーリング(I/Oイベントの待機)を扱うための新しいファイルです。NaClでは通常のepollkqueueのようなシステムコールが利用できないため、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
    }
    // ...
    
  6. 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環境で動作するために不可欠な、低レベルなインターフェースを提供します。

コアとなるコードの解説

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 rungo 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ではepollkqueueなどの効率的なメカニズムが使われますが、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.Readsyscall.Writeのようなブロッキング操作がNaClランタイムによって適切に処理されることを前提としているためと考えられます。つまり、Goのネットワークコードは、イベント駆動ではなく、ブロッキングI/Oを試み、NaClランタイムがそのブロッキングを適切に管理することを期待しているということです。
  • setDeadlineImpl: この関数は、読み書きのデッドラインを設定するためにsyscall.SetReadDeadlinesyscall.SetWriteDeadlineといったNaCl固有のシステムコールを呼び出します。

これらの変更は、GoのネットワークスタックがNaClのサンドボックス環境の制約内で機能するために、低レベルなI/O操作をNaClの提供するAPIに適合させるためのものです。

関連リンク

参考にした情報源リンク

  • 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)に基づいて技術的な詳細を解説します。

  1. 15050047: syscall: support for Native Client:

    • Goのシステムコール層にNaCl固有のシステムコールインターフェースを追加します。NaClはホストOSのシステムコールを直接呼び出すのではなく、独自のIPC (Inter-Process Communication) メカニズムであるSRPC (Simple RPC) を介して、ブラウザのプロセスやNaClランタイムと通信します。このCLは、GoのsyscallパッケージがNaClのSRPCを介して基本的なOSサービス(ファイル操作、ネットワークなど)にアクセスできるようにするための基盤を構築します。
  2. 15360044: syscall: unzip implementation for Native Client:

    • NaCl環境では、実行可能ファイルが.nexe(Native Client Executable)形式で提供されます。これは通常、複数のアーキテクチャ(x86-32, x86-64, ARM)のコードを含む「ファットバイナリ」として配布されます。このCLは、Goのランタイムが.nexeファイル内の適切なアーキテクチャのコードを「解凍」または選択して実行するためのメカニズムをsyscallパッケージ内に実装します。
  3. 15370044: syscall: Native Client SRPC implementation:

    • NaClのSRPC (Simple RPC) は、NaClモジュールがブラウザやホストシステムと通信するための主要な手段です。このCLは、Goのsyscallパッケージ内にSRPCクライアントの実装を追加し、GoプログラムがNaClの提供するサービス(例えば、ファイルシステムアクセス、ネットワークソケット操作など)を呼び出せるようにします。これは、Goの標準ライブラリがNaCl上で機能するために不可欠です。
  4. 15400047: cmd/dist, cmd/go, go/build, test: support for Native Client:

    • Goのビルドツールチェイン(cmd/dist, cmd/go)とビルドシステム(go/buildパッケージ)にNaClのサポートを追加します。これには、新しいGOOS=naclGOARCH=amd64p32(または他のNaClサポートアーキテクチャ)の組み合わせを認識し、それに応じたクロスコンパイルを可能にする変更が含まれます。また、テストフレームワークもNaCl環境で動作するように調整されます。
  5. 15410048: runtime: support for Native Client:

    • Goランタイムは、ガベージコレクション、スケジューラ、メモリ管理など、Goプログラムの実行を支える中核部分です。このCLは、NaClのサンドボックス環境の制約(例えば、直接的なメモリマップ操作の制限、特定のシステムコールへのアクセス不可など)に対応するために、ランタイムの低レベルな部分を変更します。これには、NaCl固有のメモリ割り当て、スレッド管理、シグナルハンドリングの調整が含まれます。
  6. 15410049: syscall: file descriptor table for Native Client:

    • NaClは、ホストOSのファイルディスクリプタを直接使用するのではなく、独自のファイルディスクリプタ管理システムを持っています。このCLは、Goのsyscallパッケージ内にNaClのファイルディスクリプタテーブルを管理するロジックを導入し、GoプログラムがNaClのファイルI/O操作を透過的に実行できるようにします。
  7. 15410050: syscall: in-memory file system for Native Client:

    • NaCl環境では、通常のファイルシステムアクセスが制限されるか、または存在しない場合があります。このCLは、Goのsyscallパッケージ内にインメモリファイルシステム(またはそれに類する抽象化)のサポートを追加し、Goプログラムがファイル操作を必要とする場合に、NaClのサンドボックス内で動作できるようにします。これは、Goの標準ライブラリがファイルI/Oに依存しているため重要です。
  8. 15440048: all: update +build lines for Native Client port:

    • Goのソースコード全体にわたって、+build naclタグを追加または更新し、NaCl固有のコードパスや、NaCl環境で無効化すべきコードパスを適切に制御します。これにより、GoのビルドシステムがNaClターゲット向けに正確なバイナリを生成できるようになります。
  9. 15540045: cmd/6g, cmd/8g, cmd/gc: support for Native Client:

    • Goのコンパイラ(6gはamd64、8gは386、gcは共通フロントエンド)にNaClとamd64p32アーキテクチャのサポートを追加します。これには、新しいアーキテクチャ向けのコード生成バックエンドの調整、ポインタサイズの変更(amd64p32の場合)、およびNaClのサンドボックス制約に合わせたコード最適化の調整が含まれます。特に、amd64p32ではポインタが32ビットになるため、コンパイラはアドレス計算やレジスタの使用方法を適切に調整する必要があります。
  10. 15570045: os: support for Native Client:

    • Goのosパッケージは、ファイルシステム、プロセス、環境変数など、OSレベルの機能を提供します。このCLは、osパッケージがNaCl環境の制約(例えば、fork/execの制限、特定のファイルパスへのアクセス制限など)に対応できるように変更します。
  11. 15680044: crypto/..., hash/crc32, reflect, sync/atomic: support for amd64p32:

    • amd64p32アーキテクチャは、64ビットCPU上で32ビットポインタを使用するため、アセンブリコードや低レベルな操作を行うパッケージに影響を与えます。このCLは、cryptohash/crc32reflectsync/atomicといったパッケージ内のアセンブリコードやポインタ操作を含む部分をamd64p32に対応させます。特に、reflectパッケージは型情報の内部表現にポインタサイズが影響するため、重要な変更が必要です。
  12. 15690044: net: support for Native Client:

    • Goのnetパッケージは、ネットワーク通信機能を提供します。NaCl環境では、ネットワークアクセスはブラウザのセキュリティモデルによって厳しく管理されます。このCLは、netパッケージがNaClのネットワークAPI(SRPCを介して提供される)を利用して、ソケット操作やDNSルックアップなどを実行できるように変更します。
  13. 15690048: runtime: support for fake time like on Go Playground:

    • Go Playgroundのようなサンドボックス環境では、システム時刻を直接参照するのではなく、制御された「偽の時刻」を使用することがあります。NaClもサンドボックス環境であるため、このCLはランタイムに同様の偽の時刻サポートを導入し、NaCl環境での時間関連の操作をより予測可能で安全にします。
  14. 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)に関連する変更がコアとなります。

いくつかの代表的な変更箇所を挙げます。

  1. 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;
        }
        // ...
    }
    
  2. 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"
    
  3. src/cmd/go/build.go, src/cmd/go/run.go, src/cmd/go/test.go:

    • Goのビルドツールがnaclamd64p32を新しいOS/アーキテクチャの組み合わせとして認識するように更新されています。特に、go rungo 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
    
  4. 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の制約(例えば、BPR15レジスタの使用制限、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
    // ...
    
  5. src/pkg/net/fd_poll_nacl.go:

    • NaCl環境でのファイルディスクリプタのポーリング(I/Oイベントの待機)を扱うための新しいファイルです。NaClでは通常のepollkqueueのようなシステムコールが利用できないため、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を使用
    
  6. 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環境で動作するために不可欠な、低レベルなインターフェースを提供します。

コアとなるコードの解説

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 rungo 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ではepollkqueueなどの効率的なメカニズムが使われますが、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.Readsyscall.Writeのようなブロッキング操作がNaClランタイムによって適切に処理されることを前提としているためと考えられます。つまり、Goのネットワークコードは、イベント駆動ではなく、ブロッキングI/Oを試み、NaClランタイムがそのブロッキングを適切に管理することを期待しているということです。
  • setDeadlineImpl: この関数は、読み書きのデッドラインを設定するためにsyscall.SetReadDeadlinesyscall.SetWriteDeadlineといったNaCl固有のシステムコールを呼び出します。

これらの変更は、GoのネットワークスタックがNaClのサンドボックス環境の制約内で機能するために、低レベルなI/O操作をNaClの提供するAPIに適合させるためのものです。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード (特にこのコミットのdiff)
  • Google Native Clientの公式ドキュメント (過去の資料を含む)
  • amd64p32に関する一般的な情報 (メモリモデル、ポインタサイズなど)
  • Go言語のビルドシステムと+buildタグに関するドキュメント
  • Go言語のランタイムとシステムコールに関する一般的な知識