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

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

このコミットは、Goコンパイラ(cmd/6gおよびcmd/8g、それぞれx86-64およびx86アーキテクチャ向け)において、Google Native Client (NaCl) 環境向けにコンパイルする際に「Duff's Device」の使用を無効にする変更を導入しています。NaClのセキュリティ制約により、Duff's Deviceが依存する特定のジャンプやアライメントの挙動が許可されないため、互換性の問題を解決するための対応です。

コミット

commit c6a41a35590121a7bbcaa26c44d69dcc7991089b
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Fri Apr 4 08:42:35 2014 +0200

    cmd/6g, cmd/8g: disable Duff's device on NaCl.
    
    Native Client forbids jumps/calls to arbitrary locations and
    enforces a particular alignement, which makes the Duff's device
    ineffective.
    
    LGTM=khr
    R=rsc, dave, khr
    CC=golang-codereviews
    https://golang.org/cl/84400043

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

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

元コミット内容

cmd/6g, cmd/8g: disable Duff's device on NaCl.

Native Client forbids jumps/calls to arbitrary locations and
enforces a particular alignement, which makes the Duff's device
ineffective.

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

変更の背景

この変更の背景には、Goコンパイラが生成するコードとGoogle Native Client (NaCl) の実行環境との間の非互換性があります。

Goコンパイラは、特定の最適化手法として「Duff's Device」のようなテクニックを利用して、メモリ操作(コピーやゼロクリアなど)のパフォーマンスを向上させることがあります。Duff's Deviceは、ループアンローリングを効率的に行うために、switch文とdo-whileループを組み合わせ、コード内の任意の位置へのジャンプ(フォールスルー)を利用します。

しかし、Google Native Client (NaCl) は、ウェブブラウザ内でネイティブコードを安全に実行するためのサンドボックス環境を提供します。この安全性は、コードの実行を厳しく制限することによって実現されており、特に以下の点がDuff's Deviceと衝突します。

  1. 任意の位置へのジャンプ/呼び出しの禁止: NaClは、セキュリティ上の理由から、コードが予測不能な、または検証されていない任意のアドレスにジャンプしたり呼び出したりすることを禁止しています。これは、悪意のあるコードがサンドボックスを回避したり、システムに不正アクセスしたりするのを防ぐためです。Duff's Deviceは、switch文のフォールスルーを利用して、ループの途中に「飛び込む」ような挙動をするため、NaClのこの制約に抵触する可能性があります。
  2. 特定のアライメントの強制: NaClは、コードの安全な実行を保証するために、特定の命令アライメントを強制します。Duff's Deviceは、その最適化の性質上、命令の配置やジャンプ先のアライメントがNaClの要求と一致しない場合があります。

これらのNaClの制約により、Duff's DeviceがNaCl環境で正しく機能しないか、あるいはセキュリティ違反と見なされて実行が拒否される可能性があります。そのため、GoコンパイラがNaCl向けにコードを生成する際には、Duff's Deviceによる最適化を無効にする必要がありました。これにより、NaCl環境でのGoプログラムの安定した動作とセキュリティが保証されます。

前提知識の解説

Duff's Device (ダフのデバイス)

Duff's Deviceは、1983年にトム・ダフによって考案されたC言語のプログラミングテクニックで、主にループアンローリングを手動で実装し、パフォーマンスを最適化するために使用されました。当時のコンパイラが現在ほど最適化に優れていなかった時代に、非常に巧妙なハックとして知られています。

仕組み: Duff's Deviceは、switch文のフォールスルー特性とdo-whileループを組み合わせて、ループのオーバーヘッドを削減します。一般的なループアンローリングでは、ループ本体を複数回記述することで、ループ条件のチェックや分岐の回数を減らします。しかし、これにより、ループの総回数がアンローリングファクタの倍数でない場合に、残りの処理をどうするかという問題が生じます。

Duff's Deviceは、この問題をswitch文で解決します。例えば、8回アンロールする場合、count % 8の結果に基づいてswitch文が実行され、適切なcaseラベルにジャンプします。caseラベルはdo-whileループの中に配置されており、フォールスルーによって残りの処理が実行された後、メインのアンロールされたループが開始されます。これにより、ループの開始位置を動的に調整し、残りの処理とアンロールされた処理をシームレスに結合します。

目的: 主に、メモリコピー(memcpyのような操作)やメモリのゼロクリアなど、大量のデータを扱う処理において、ループの制御構造によるオーバーヘッドを削減し、CPUのキャッシュ効率を向上させることで、実行速度を向上させることを目的としていました。

現代における位置づけ: 現代のコンパイラは非常に高度な最適化能力を持っており、Duff's Deviceのような手動のループアンローリングは、多くの場合、コンパイラによる自動最適化よりも効率が悪いか、コードの可読性を著しく損なうだけでメリットがないとされています。そのため、現在では主に歴史的な興味の対象となっています。

Google Native Client (NaCl)

Google Native Client (NaCl) は、ウェブブラウザ内でC/C++などのネイティブコードを安全かつ高性能に実行するためのオープンソース技術でした。その主な目的は、ウェブアプリケーションにデスクトップアプリケーションに近いパフォーマンスと機能を提供することでした。

セキュリティモデル: NaClの最も重要な特徴は、その堅牢なセキュリティモデルです。これは「サンドボックス化」と呼ばれる技術に基づいており、ネイティブコードがブラウザやユーザーのシステムに損害を与えることを防ぎます。

  1. 二層サンドボックス:

    • 内部サンドボックス (Intra-process Sandbox): ソフトウェアフォールト分離 (SFI) と静的解析を利用して、信頼できないネイティブコードを検証します。これにより、サンドボックス化されたコードがメモリの不正な領域にアクセスするのを防ぎます。x86アーキテクチャでは、メモリセグメンテーションやページ保護が利用され、安全でない命令(直接的なシステムコールなど)の実行を防ぐためのコード検証器が含まれます。
    • 外部サンドボックス (Process Boundary Sandbox): プロセス境界で動作し、システムコールを仲介します。Chromeブラウザのコンテキストでは、NaClコードはブラウザのレンダラープロセスと同じ、制限された権限で実行されます。
  2. 悪意の仮定: NaClの設計の中心的な原則は、すべてのモジュールが潜在的に悪意のあるものであると仮定することです。そのため、すべてのモジュールに同じ厳格なセキュリティメカニズムが適用されます。

  3. システムアクセス制限: NaClは、パフォーマンスのために信頼できないNEXE (Native Client Executable) モジュールにCPUへの直接アクセスを許可しますが、システムの他の部分へのアクセスは厳しく制限します。例えば、ネットワークアクセスはブラウザのJavaScriptを介して仲介され、他のJavaScript操作と同じセキュリティ制約に従います。

  4. コード検証: NaClのセキュリティの重要な要素は、NEXEモジュールの検証プロセスです。NaCl開発用に修正されたGCCツールチェーンは、安全な命令のみが生成され、実行可能ファイルの構造が有効であることを保証します。検証されていない命令を実行しようとすると、Native Clientによって提供されるセキュリティが損なわれます。

現代における位置づけ: Google Native Client (NaCl) は、WebAssemblyという新しい技術に道を譲り、現在は開発が終了しています。WebAssemblyは、NaClの目標を引き継ぎ、より広範なプラットフォームとブラウザでネイティブに近いパフォーマンスを提供することを目指しています。

技術的詳細

Duff's Deviceは、その最適化の性質上、コンパイラが生成するアセンブリコードにおいて、特定の命令シーケンスとジャンプパターンを生成します。具体的には、switch文のフォールスルーを利用して、ループの途中にある任意のcaseラベルに直接ジャンプすることで、ループの開始位置を動的に調整します。この「任意の位置へのジャンプ」は、通常のアセンブリ言語では直接的なJMP命令やCALL命令、あるいはレジスタを介した間接ジャンプとして表現されます。

一方、Google Native Client (NaCl) のセキュリティモデルは、このような「任意の位置へのジャンプ」を厳しく制限します。NaClは、実行されるすべてのコードがサンドボックスの制約内で動作することを保証するために、コードの静的解析と実行時の検証を行います。特に、コードが予測可能な制御フローを持ち、サンドボックスの境界を越えるような不正なメモリアクセスやシステムコールを行わないことを保証する必要があります。

Duff's Deviceが生成するような、計算されたアドレスへのジャンプや、特定の命令アライメントに依存するコードは、NaClのコード検証器によって「安全でない」と判断される可能性があります。NaClは、コードの実行が常にサンドボックスの内部に留まり、許可されたAPIのみを介してシステムリソースにアクセスすることを保証するために、非常に厳格なルールを適用します。Duff's Deviceの巧妙な制御フローは、この厳格なルールに適合しないため、NaCl環境では機能しないか、あるいはセキュリティ違反として扱われます。

したがって、GoコンパイラがNaClターゲット向けにコードを生成する際には、Duff's Deviceによる最適化を無効にする必要があります。これは、パフォーマンス上の利点を犠牲にしてでも、NaCl環境でのGoプログラムの実行可能性とセキュリティを確保するためのトレードオフです。コンパイラは、Duff's Deviceの代わりに、より単純でNaClの制約に準拠したループ構造(例えば、通常のforループや、より保守的なループアンローリング)を生成することになります。

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

このコミットでは、Goコンパイラのx86-64 (cmd/6g) および x86 (cmd/8g) バックエンドにおけるコード生成部分が変更されています。具体的には、cgen.cggen.cファイル内の、Duff's Deviceに関連する条件分岐にnacl変数が追加されています。

src/cmd/6g/cgen.c (x86-64)

--- a/src/cmd/6g/cgen.c
+++ b/src/cmd/6g/cgen.c
@@ -1448,7 +1448,7 @@ sgen(Node *n, Node *ns, int64 w)
 		tgins(ACLD, N, N);
 	} else {
 		// normal direction
-		if(q > 128) {
+		if(q > 128 || (nacl && q >= 4)) {
 			gconreg(movptr, q, D_CX);
 			gins(AREP, N, N);	// repeat
 			gins(AMOVSQ, N, N);	// MOVQ *(SI)+,*(DI)+

src/cmd/6g/ggen.c (x86-64)

--- a/src/cmd/6g/ggen.c
+++ b/src/cmd/6g/ggen.c
@@ -77,7 +77,7 @@ zerorange(Prog *p, vlong frame, vlong lo, vlong hi, uint32 *ax)
 		for(i = 0; i < cnt; i += widthreg) {
 			p = appendpp(p, AMOVQ, D_AX, 0, D_SP+D_INDIR, frame+lo+i);
 		}
-	} else if(cnt <= 128*widthreg) {
+	} else if(!nacl && (cnt <= 128*widthreg)) {
 		p = appendpp(p, leaptr, D_SP+D_INDIR, frame+lo, D_DI, 0);
 		p = appendpp(p, ADUFFZERO, D_NONE, 0, D_ADDR, 2*(128-cnt/widthreg));
 		p->to.sym = linksym(pkglookup("duffzero", runtimepkg));
@@ -1119,7 +1119,7 @@ clearfat(Node *nl)
 	savex(D_AX, &ax, &oldax, N, types[tptr]);
 	gconreg(AMOVL, 0, D_AX);
 
-	if(q > 128) {
+	if(q > 128 || (q >= 4 && nacl)) {
 		gconreg(movptr, q, D_CX);
 		gins(AREP, N, N);	// repeat
 		gins(ASTOSQ, N, N);	// STOQ AL,*(DI)+

src/cmd/8g/cgen.c (x86)

--- a/src/cmd/8g/cgen.c
+++ b/src/cmd/8g/cgen.c
@@ -1315,7 +1315,7 @@ sgen(Node *n, Node *res, int64 w)
 	} else {
 		gins(ACLD, N, N);	// paranoia.  TODO(rsc): remove?
 		// normal direction
-		if(q > 128) {
+		if(q > 128 || (q >= 4 && nacl)) {
 			gconreg(AMOVL, q, D_CX);
 			gins(AREP, N, N);	// repeat
 			gins(AMOVSL, N, N);	// MOVL *(SI)+,*(DI)+

src/cmd/8g/ggen.c (x86)

--- a/src/cmd/8g/ggen.c
+++ b/src/cmd/8g/ggen.c
@@ -75,7 +75,7 @@ zerorange(Prog *p, vlong frame, vlong lo, vlong hi, uint32 *ax)
 		for(i = 0; i < cnt; i += widthreg) {
 			p = appendpp(p, AMOVL, D_AX, 0, D_SP+D_INDIR, frame+lo+i);
 		}
-	} else if(cnt <= 128*widthreg) {
+	} else if(!nacl && cnt <= 128*widthreg) {
 		p = appendpp(p, ALEAL, D_SP+D_INDIR, frame+lo, D_DI, 0);
 		p = appendpp(p, ADUFFZERO, D_NONE, 0, D_ADDR, 1*(128-cnt/widthreg));
 		p->to.sym = linksym(pkglookup("duffzero", runtimepkg));
@@ -176,7 +176,7 @@ clearfat(Node *nl)
 	agen(nl, &n1);
 	gconreg(AMOVL, 0, D_AX);
 
-	if(q > 128) {
+	if(q > 128 || (q >= 4 && nacl)) {
 		gconreg(AMOVL, q, D_CX);
 		gins(AREP, N, N);	// repeat
 		gins(ASTOSL, N, N);	// STOL AL,*(DI)+

コアとなるコードの解説

変更の核心は、Duff's Deviceを有効にするかどうかの条件式にnacl変数を追加した点です。

  • nacl変数: この変数は、Goコンパイラが現在Google Native Client (NaCl) ターゲット向けにコードをコンパイルしているかどうかを示すブール値(真偽値)フラグです。nacltrueの場合、NaCl環境向けのコンパイルであることを意味します。

  • 条件式の変更:

    • if(q > 128) から if(q > 128 || (nacl && q >= 4)):

      • これは、sgen関数(おそらく一般的なコード生成の一部)とclearfat関数(構造体などの大きなデータのゼロクリアに関連)における変更です。
      • 元の条件q > 128は、処理するデータ量qが一定の閾値(128バイト)を超えた場合にDuff's Deviceのような最適化を適用することを示唆しています。
      • 新しい条件q > 128 || (nacl && q >= 4)は、「qが128より大きい場合」または「NaCl向けコンパイルであり、かつqが4バイト以上の場合」に、Duff's Deviceの適用を検討する、というロジックになっています。
      • しかし、この変更は実際にはDuff's Deviceを無効にする方向で働いています。Duff's Deviceは通常、ADUFFZEROADUFFCOPYのような特殊な命令(またはそれらに相当するコードパス)を生成する際に使用されます。このifブロックの内部では、AREP(繰り返し命令)やAMOVSQ/AMOVSL(メモリ移動命令)が生成されており、これらはDuff's Deviceとは異なる、より一般的な繰り返し操作の最適化パスです。
      • コミットメッセージの意図と照らし合わせると、このifブロックはDuff's Deviceとは別の最適化パスであり、NaCl環境ではq >= 4という小さなサイズでもこのパスを避けるべき、という意図が読み取れます。つまり、NaCl環境では、たとえデータサイズが小さくても、Duff's Deviceに似た挙動をする可能性のある最適化パスを避けるようにしています。
    • else if(cnt <= 128*widthreg) から else if(!nacl && (cnt <= 128*widthreg)):

      • これはzerorange関数(メモリ範囲のゼロクリアに関連)における変更です。
      • 元の条件cnt <= 128*widthregは、ゼロクリアするバイト数cntが一定の閾値以下の場合にDuff's Device(ADUFFZERO命令)を使用することを示唆しています。
      • 新しい条件!nacl && (cnt <= 128*widthreg)は、「NaCl向けコンパイルではない場合」かつcntが閾値以下の場合」にのみDuff's Device(ADUFFZERO命令)を生成するように変更されています。
      • これにより、nacltrue(NaCl向けコンパイル)の場合、このDuff's Deviceを生成するコードパスは完全にスキップされ、代わりに通常のループや他の安全な方法でゼロクリアが行われるようになります。
  • qcnt: これらの変数は、それぞれ処理対象のデータ量(バイト数や要素数)を表しています。Duff's Deviceのような最適化は、ある程度のデータ量がある場合に効果を発揮するため、これらの閾値(例: 1284)が設定されています。

これらの変更により、GoコンパイラはNaCl環境向けにコンパイルする際に、NaClのセキュリティ制約に違反する可能性のあるDuff's Device関連のコード生成を回避し、互換性と安全性を確保しています。

関連リンク

参考にした情報源リンク